mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-05-16 01:12:46 +00:00
refactor: add cross-platform utility functions
This commit is contained in:
7
server/src/shared/platform/index.ts
Normal file
7
server/src/shared/platform/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// This barrel keeps future imports short while the backend migrates into TypeScript.
|
||||
export * from './path.js';
|
||||
export * from './runtime-platform.js';
|
||||
export * from './shell.js';
|
||||
export * from './stream.js';
|
||||
export * from './text.js';
|
||||
export * from './types.js';
|
||||
29
server/src/shared/platform/path.test.ts
Normal file
29
server/src/shared/platform/path.test.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import { arePathsEquivalent, normalizePathForPlatform, toPortablePath } from './path.js';
|
||||
|
||||
// This test verifies path strings can be normalized for logs and platform-specific execution.
|
||||
test('path helpers normalize separators in both directions', () => {
|
||||
assert.equal(toPortablePath('folder\\child\\file.txt'), 'folder/child/file.txt');
|
||||
assert.equal(
|
||||
normalizePathForPlatform('folder\\child/file.txt', 'windows'),
|
||||
'folder\\child\\file.txt',
|
||||
);
|
||||
assert.equal(
|
||||
normalizePathForPlatform('folder\\child/file.txt', 'linux'),
|
||||
'folder/child/file.txt',
|
||||
);
|
||||
});
|
||||
|
||||
// This test verifies path comparison respects Windows case-insensitivity but POSIX case-sensitivity.
|
||||
test('arePathsEquivalent follows the case rules of the target platform', () => {
|
||||
assert.equal(
|
||||
arePathsEquivalent('C:\\Repo\\File.txt', 'c:/repo/file.txt', 'windows'),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
arePathsEquivalent('/repo/File.txt', '/repo/file.txt', 'linux'),
|
||||
false,
|
||||
);
|
||||
});
|
||||
34
server/src/shared/platform/path.ts
Normal file
34
server/src/shared/platform/path.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import path from 'path';
|
||||
|
||||
import { getPlatformPathSeparator, isWindowsPlatform, resolveRuntimePlatform } from './runtime-platform.js';
|
||||
import type { RuntimePlatform } from './types.js';
|
||||
|
||||
// This helper converts paths into a portable slash-separated form for logs, keys, and serialized payloads.
|
||||
export function toPortablePath(value: string): string {
|
||||
return value.replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
// This helper rewrites any mixture of separators into the preferred style for the target platform.
|
||||
export function normalizePathForPlatform(
|
||||
value: string,
|
||||
platform: RuntimePlatform = resolveRuntimePlatform(),
|
||||
): string {
|
||||
const separator = getPlatformPathSeparator(platform);
|
||||
return value.replace(/[\\/]+/g, separator);
|
||||
}
|
||||
|
||||
// This helper compares paths using the case-sensitivity rules of the target platform.
|
||||
export function arePathsEquivalent(
|
||||
left: string,
|
||||
right: string,
|
||||
platform: RuntimePlatform = resolveRuntimePlatform(),
|
||||
): boolean {
|
||||
// This branch uses the target platform's path semantics instead of the host machine's semantics.
|
||||
const pathModule = isWindowsPlatform(platform) ? path.win32 : path.posix;
|
||||
const normalizedLeft = pathModule.normalize(normalizePathForPlatform(left, platform));
|
||||
const normalizedRight = pathModule.normalize(normalizePathForPlatform(right, platform));
|
||||
|
||||
return isWindowsPlatform(platform)
|
||||
? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase()
|
||||
: normalizedLeft === normalizedRight;
|
||||
}
|
||||
27
server/src/shared/platform/runtime-platform.test.ts
Normal file
27
server/src/shared/platform/runtime-platform.test.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import {
|
||||
getPlatformLineEnding,
|
||||
getPlatformPathSeparator,
|
||||
isWindowsPlatform,
|
||||
resolveRuntimePlatform,
|
||||
} from './runtime-platform.js';
|
||||
|
||||
// This test covers the platform vocabulary used by the adapter layer.
|
||||
test('resolveRuntimePlatform maps Node platforms into adapter platforms', () => {
|
||||
assert.equal(resolveRuntimePlatform('win32'), 'windows');
|
||||
assert.equal(resolveRuntimePlatform('darwin'), 'macos');
|
||||
assert.equal(resolveRuntimePlatform('linux'), 'linux');
|
||||
assert.equal(resolveRuntimePlatform('freebsd'), 'linux');
|
||||
});
|
||||
|
||||
// This test verifies the shared helpers expose the expected OS defaults.
|
||||
test('platform helpers expose the expected line endings and separators', () => {
|
||||
assert.equal(isWindowsPlatform('windows'), true);
|
||||
assert.equal(isWindowsPlatform('linux'), false);
|
||||
assert.equal(getPlatformLineEnding('windows'), 'crlf');
|
||||
assert.equal(getPlatformLineEnding('linux'), 'lf');
|
||||
assert.equal(getPlatformPathSeparator('windows'), '\\');
|
||||
assert.equal(getPlatformPathSeparator('macos'), '/');
|
||||
});
|
||||
29
server/src/shared/platform/runtime-platform.ts
Normal file
29
server/src/shared/platform/runtime-platform.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { LineEnding, RuntimePlatform } from './types.js';
|
||||
|
||||
// This function maps Node's platform strings into the smaller vocabulary used by the adapter layer.
|
||||
export function resolveRuntimePlatform(nodePlatform: NodeJS.Platform = process.platform): RuntimePlatform {
|
||||
switch (nodePlatform) {
|
||||
case 'win32':
|
||||
return 'windows';
|
||||
case 'darwin':
|
||||
return 'macos';
|
||||
default:
|
||||
// Every non-Windows, non-macOS platform in this project behaves like a POSIX shell target.
|
||||
return 'linux';
|
||||
}
|
||||
}
|
||||
|
||||
// This helper keeps Windows checks readable at call sites.
|
||||
export function isWindowsPlatform(platform: RuntimePlatform = resolveRuntimePlatform()): boolean {
|
||||
return platform === 'windows';
|
||||
}
|
||||
|
||||
// This helper centralizes the preferred newline style for each platform.
|
||||
export function getPlatformLineEnding(platform: RuntimePlatform = resolveRuntimePlatform()): LineEnding {
|
||||
return isWindowsPlatform(platform) ? 'crlf' : 'lf';
|
||||
}
|
||||
|
||||
// This helper centralizes the preferred path separator for each platform.
|
||||
export function getPlatformPathSeparator(platform: RuntimePlatform = resolveRuntimePlatform()): '\\' | '/' {
|
||||
return isWindowsPlatform(platform) ? '\\' : '/';
|
||||
}
|
||||
43
server/src/shared/platform/shell.test.ts
Normal file
43
server/src/shared/platform/shell.test.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import { buildFallbackCommand, createShellSpawnPlan, quoteShellArgument } from './shell.js';
|
||||
|
||||
// This test verifies the backend can ask for a shell launch without branching on the OS at every call site.
|
||||
test('createShellSpawnPlan returns the expected executable and argv per platform', () => {
|
||||
assert.deepEqual(createShellSpawnPlan('echo hello', 'windows'), {
|
||||
platform: 'windows',
|
||||
executable: 'powershell.exe',
|
||||
args: ['-Command', 'echo hello'],
|
||||
commandFlag: '-Command',
|
||||
preferredLineEnding: 'crlf',
|
||||
pathSeparator: '\\',
|
||||
});
|
||||
|
||||
assert.deepEqual(createShellSpawnPlan('echo hello', 'linux'), {
|
||||
platform: 'linux',
|
||||
executable: 'bash',
|
||||
args: ['-c', 'echo hello'],
|
||||
commandFlag: '-c',
|
||||
preferredLineEnding: 'lf',
|
||||
pathSeparator: '/',
|
||||
});
|
||||
});
|
||||
|
||||
// This test verifies shell quoting rules stay isolated inside the adapter layer.
|
||||
test('quoteShellArgument escapes embedded single quotes correctly', () => {
|
||||
assert.equal(quoteShellArgument("it's", 'windows'), "'it''s'");
|
||||
assert.equal(quoteShellArgument("it's", 'linux'), `'it'"'"'s'`);
|
||||
});
|
||||
|
||||
// This test verifies resume-or-fallback command composition stays platform-specific in one helper.
|
||||
test('buildFallbackCommand emits PowerShell or POSIX fallback syntax', () => {
|
||||
assert.equal(
|
||||
buildFallbackCommand("codex resume '123'", 'codex', 'windows'),
|
||||
"codex resume '123'; if ($LASTEXITCODE -ne 0) { codex }",
|
||||
);
|
||||
assert.equal(
|
||||
buildFallbackCommand("codex resume '123'", 'codex', 'linux'),
|
||||
"codex resume '123' || codex",
|
||||
);
|
||||
});
|
||||
55
server/src/shared/platform/shell.ts
Normal file
55
server/src/shared/platform/shell.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { getPlatformLineEnding, getPlatformPathSeparator, resolveRuntimePlatform } from './runtime-platform.js';
|
||||
import type { RuntimePlatform, ShellSpawnPlan } from './types.js';
|
||||
|
||||
// This helper returns the shell executable and argv shape for the target platform.
|
||||
export function createShellSpawnPlan(
|
||||
command: string,
|
||||
platform: RuntimePlatform = resolveRuntimePlatform(),
|
||||
): ShellSpawnPlan {
|
||||
if (platform === 'windows') {
|
||||
return {
|
||||
platform,
|
||||
executable: 'powershell.exe',
|
||||
args: ['-Command', command],
|
||||
commandFlag: '-Command',
|
||||
preferredLineEnding: getPlatformLineEnding(platform),
|
||||
pathSeparator: getPlatformPathSeparator(platform),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
platform,
|
||||
executable: 'bash',
|
||||
args: ['-c', command],
|
||||
commandFlag: '-c',
|
||||
preferredLineEnding: getPlatformLineEnding(platform),
|
||||
pathSeparator: getPlatformPathSeparator(platform),
|
||||
};
|
||||
}
|
||||
|
||||
// This helper quotes one argument so the caller does not need to remember shell-specific escaping rules.
|
||||
export function quoteShellArgument(
|
||||
value: string,
|
||||
platform: RuntimePlatform = resolveRuntimePlatform(),
|
||||
): string {
|
||||
if (platform === 'windows') {
|
||||
// PowerShell escapes a single quote inside a single-quoted string by doubling it.
|
||||
return `'${value.replace(/'/g, "''")}'`;
|
||||
}
|
||||
|
||||
// POSIX shells escape a single quote by closing the string, injecting an escaped quote, and reopening it.
|
||||
return `'${value.replace(/'/g, "'\"'\"'")}'`;
|
||||
}
|
||||
|
||||
// This helper builds the platform-specific "try primary, then fallback" shell expression.
|
||||
export function buildFallbackCommand(
|
||||
primaryCommand: string,
|
||||
fallbackCommand: string,
|
||||
platform: RuntimePlatform = resolveRuntimePlatform(),
|
||||
): string {
|
||||
if (platform === 'windows') {
|
||||
return `${primaryCommand}; if ($LASTEXITCODE -ne 0) { ${fallbackCommand} }`;
|
||||
}
|
||||
|
||||
return `${primaryCommand} || ${fallbackCommand}`;
|
||||
}
|
||||
41
server/src/shared/platform/stream.test.ts
Normal file
41
server/src/shared/platform/stream.test.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import { createStreamLineAccumulator } from './stream.js';
|
||||
|
||||
// This test verifies CRLF split across chunk boundaries does not create a fake empty line.
|
||||
test('createStreamLineAccumulator handles CRLF across chunk boundaries', () => {
|
||||
const accumulator = createStreamLineAccumulator();
|
||||
|
||||
assert.deepEqual(accumulator.push('first\r'), []);
|
||||
assert.deepEqual(accumulator.push('\nsecond\r\nthird'), ['first', 'second']);
|
||||
assert.deepEqual(accumulator.flush(), ['third']);
|
||||
});
|
||||
|
||||
// This test verifies the first chunk can safely contain a UTF-8 BOM.
|
||||
test('createStreamLineAccumulator strips a BOM from the first chunk only', () => {
|
||||
const accumulator = createStreamLineAccumulator();
|
||||
|
||||
assert.deepEqual(accumulator.push(Buffer.from('\uFEFFalpha\nbeta')), ['alpha']);
|
||||
assert.deepEqual(accumulator.flush(), ['beta']);
|
||||
});
|
||||
|
||||
// This test verifies callers can intentionally drop empty lines when parsing command output.
|
||||
test('createStreamLineAccumulator can discard empty lines', () => {
|
||||
const accumulator = createStreamLineAccumulator({ preserveEmptyLines: false });
|
||||
|
||||
assert.deepEqual(accumulator.push('one\n\n'), ['one']);
|
||||
assert.deepEqual(accumulator.push('two\r\n\r\nthree'), ['two']);
|
||||
assert.deepEqual(accumulator.flush(), ['three']);
|
||||
});
|
||||
|
||||
// This test verifies the parser can be reused for a second stream after reset.
|
||||
test('createStreamLineAccumulator reset clears the internal buffer', () => {
|
||||
const accumulator = createStreamLineAccumulator();
|
||||
|
||||
assert.deepEqual(accumulator.push('partial'), []);
|
||||
assert.equal(accumulator.peek(), 'partial');
|
||||
accumulator.reset();
|
||||
assert.equal(accumulator.peek(), '');
|
||||
assert.deepEqual(accumulator.push('done\n'), ['done']);
|
||||
});
|
||||
98
server/src/shared/platform/stream.ts
Normal file
98
server/src/shared/platform/stream.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { stripUtf8Bom } from './text.js';
|
||||
import type { StreamLineAccumulator, StreamLineAccumulatorOptions } from './types.js';
|
||||
|
||||
// This helper keeps the push logic focused on line extraction rather than Buffer/string branching.
|
||||
function chunkToString(chunk: Buffer | string): string {
|
||||
return Buffer.isBuffer(chunk) ? chunk.toString('utf8') : chunk;
|
||||
}
|
||||
|
||||
// This helper lets callers reuse the same cross-platform line parser for stdout, stderr, or file streams.
|
||||
export function createStreamLineAccumulator(
|
||||
options: StreamLineAccumulatorOptions = {},
|
||||
): StreamLineAccumulator {
|
||||
const { preserveEmptyLines = true } = options;
|
||||
let buffer = '';
|
||||
let isFirstChunk = true;
|
||||
|
||||
// This helper applies BOM stripping only once because a stream can only start once.
|
||||
const normalizeIncomingChunk = (chunk: Buffer | string): string => {
|
||||
const text = chunkToString(chunk);
|
||||
|
||||
if (!isFirstChunk) {
|
||||
return text;
|
||||
}
|
||||
|
||||
isFirstChunk = false;
|
||||
return stripUtf8Bom(text);
|
||||
};
|
||||
|
||||
// This helper enforces the caller's empty-line policy in one place.
|
||||
const maybeAppendLine = (lines: string[], line: string): void => {
|
||||
if (preserveEmptyLines || line.length > 0) {
|
||||
lines.push(line);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
// This method extracts only complete lines and keeps an incomplete trailing fragment in memory.
|
||||
push: (chunk: Buffer | string): string[] => {
|
||||
buffer += normalizeIncomingChunk(chunk);
|
||||
const lines: string[] = [];
|
||||
let lineStartIndex = 0;
|
||||
let cursor = 0;
|
||||
|
||||
while (cursor < buffer.length) {
|
||||
const currentCharacter = buffer[cursor];
|
||||
|
||||
if (currentCharacter === '\n') {
|
||||
maybeAppendLine(lines, buffer.slice(lineStartIndex, cursor));
|
||||
cursor += 1;
|
||||
lineStartIndex = cursor;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentCharacter === '\r') {
|
||||
// A trailing carriage return may be the first half of a CRLF sequence from the next chunk.
|
||||
if (cursor === buffer.length - 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
maybeAppendLine(lines, buffer.slice(lineStartIndex, cursor));
|
||||
cursor += buffer[cursor + 1] === '\n' ? 2 : 1;
|
||||
lineStartIndex = cursor;
|
||||
continue;
|
||||
}
|
||||
|
||||
cursor += 1;
|
||||
}
|
||||
|
||||
buffer = buffer.slice(lineStartIndex);
|
||||
return lines;
|
||||
},
|
||||
|
||||
// This method flushes the final unterminated fragment when the stream closes.
|
||||
flush: (): string[] => {
|
||||
if (buffer === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const trailingLine = buffer.endsWith('\r') ? buffer.slice(0, -1) : buffer;
|
||||
buffer = '';
|
||||
|
||||
if (!preserveEmptyLines && trailingLine.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [trailingLine];
|
||||
},
|
||||
|
||||
// This method exposes the buffered partial fragment for diagnostics or advanced callers.
|
||||
peek: (): string => buffer,
|
||||
|
||||
// This method resets the parser so a caller can reuse the same object for a new stream.
|
||||
reset: (): void => {
|
||||
buffer = '';
|
||||
isFirstChunk = true;
|
||||
},
|
||||
};
|
||||
}
|
||||
46
server/src/shared/platform/text.test.ts
Normal file
46
server/src/shared/platform/text.test.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import {
|
||||
detectLineEnding,
|
||||
normalizeLineEndings,
|
||||
normalizeTerminalInput,
|
||||
normalizeTextForParsing,
|
||||
preserveExistingLineEndings,
|
||||
splitLines,
|
||||
stripUtf8Bom,
|
||||
} from './text.js';
|
||||
|
||||
// This test verifies the parser can consume mixed OS line endings as one stable LF format.
|
||||
test('normalizeTextForParsing converts CRLF and CR into LF', () => {
|
||||
assert.equal(normalizeTextForParsing('a\r\nb\rc\n'), 'a\nb\nc\n');
|
||||
});
|
||||
|
||||
// This test verifies BOM stripping and explicit output line-ending control.
|
||||
test('normalizeLineEndings strips a UTF-8 BOM and can emit CRLF', () => {
|
||||
assert.equal(stripUtf8Bom('\uFEFFhello'), 'hello');
|
||||
assert.equal(normalizeLineEndings('\uFEFFa\nb', 'crlf'), 'a\r\nb');
|
||||
});
|
||||
|
||||
// This test verifies callers can opt into preserving or trimming empty lines explicitly.
|
||||
test('splitLines supports empty-line preservation and trailing-line trimming', () => {
|
||||
assert.deepEqual(splitLines('a\r\n\r\nb\r\n'), ['a', '', 'b', '']);
|
||||
assert.deepEqual(
|
||||
splitLines('a\r\n\r\nb\r\n', {
|
||||
preserveEmptyLines: false,
|
||||
trimTrailingEmptyLine: true,
|
||||
}),
|
||||
['a', 'b'],
|
||||
);
|
||||
});
|
||||
|
||||
// This test verifies file rewrites can preserve the line-ending style already present on disk.
|
||||
test('preserveExistingLineEndings reuses the current file style', () => {
|
||||
assert.equal(detectLineEnding('a\r\nb\r\n'), 'crlf');
|
||||
assert.equal(preserveExistingLineEndings('x\ny', 'a\r\nb\r\n'), 'x\r\ny');
|
||||
});
|
||||
|
||||
// This test verifies pasted terminal input is normalized into carriage returns for PTY writes.
|
||||
test('normalizeTerminalInput converts mixed newlines into carriage returns', () => {
|
||||
assert.equal(normalizeTerminalInput('one\r\ntwo\nthree\rfour'), 'one\rtwo\rthree\rfour');
|
||||
});
|
||||
55
server/src/shared/platform/text.ts
Normal file
55
server/src/shared/platform/text.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import type { LineEnding, SplitLinesOptions } from './types.js';
|
||||
|
||||
// This constant is the UTF-8 byte order mark represented as a JavaScript string.
|
||||
const UTF8_BOM = '\uFEFF';
|
||||
|
||||
// This helper removes a UTF-8 BOM because it breaks parsers that expect plain text or JSON at byte zero.
|
||||
export function stripUtf8Bom(value: string): string {
|
||||
return value.startsWith(UTF8_BOM) ? value.slice(1) : value;
|
||||
}
|
||||
|
||||
// This helper turns any mixture of CRLF, LF, or legacy CR endings into one explicit target format.
|
||||
export function normalizeLineEndings(value: string, target: LineEnding = 'lf'): string {
|
||||
const withoutBom = stripUtf8Bom(value);
|
||||
const asLf = withoutBom.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
||||
|
||||
return target === 'crlf' ? asLf.replace(/\n/g, '\r\n') : asLf;
|
||||
}
|
||||
|
||||
// This helper infers the dominant file style so later writes can preserve the existing convention.
|
||||
export function detectLineEnding(value: string): LineEnding {
|
||||
return value.includes('\r\n') ? 'crlf' : 'lf';
|
||||
}
|
||||
|
||||
// This helper splits text into logical lines after normalizing line endings first.
|
||||
export function splitLines(value: string, options: SplitLinesOptions = {}): string[] {
|
||||
const { preserveEmptyLines = true, trimTrailingEmptyLine = false } = options;
|
||||
const normalized = normalizeLineEndings(value, 'lf');
|
||||
const lines = normalized.split('\n');
|
||||
const trimmedLines =
|
||||
trimTrailingEmptyLine && lines.at(-1) === ''
|
||||
? lines.slice(0, -1)
|
||||
: lines;
|
||||
|
||||
return preserveEmptyLines ? trimmedLines : trimmedLines.filter((line) => line.length > 0);
|
||||
}
|
||||
|
||||
// This helper gives parsers one stable newline format regardless of the source operating system.
|
||||
export function normalizeTextForParsing(value: string): string {
|
||||
return normalizeLineEndings(value, 'lf');
|
||||
}
|
||||
|
||||
// This helper prepares text for file output when the caller wants to force a specific line-ending style.
|
||||
export function normalizeTextForFileWrite(value: string, lineEnding: LineEnding): string {
|
||||
return normalizeLineEndings(value, lineEnding);
|
||||
}
|
||||
|
||||
// This helper keeps file rewrites stable by reusing the line-ending style already present on disk.
|
||||
export function preserveExistingLineEndings(nextText: string, currentText: string): string {
|
||||
return normalizeTextForFileWrite(nextText, detectLineEnding(currentText));
|
||||
}
|
||||
|
||||
// This helper converts pasted or synthetic input into the carriage-return form PTYs expect for Enter.
|
||||
export function normalizeTerminalInput(value: string): string {
|
||||
return stripUtf8Bom(value).replace(/\r\n|\n|\r/g, '\r');
|
||||
}
|
||||
34
server/src/shared/platform/types.ts
Normal file
34
server/src/shared/platform/types.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
// This type keeps the rest of the backend independent from Node's raw platform names.
|
||||
export type RuntimePlatform = 'windows' | 'linux' | 'macos';
|
||||
|
||||
// This type makes line-ending intent explicit in parser and file-write code.
|
||||
export type LineEnding = 'lf' | 'crlf';
|
||||
|
||||
// This type describes how to launch a shell without leaking OS-specific details.
|
||||
export type ShellSpawnPlan = {
|
||||
platform: RuntimePlatform;
|
||||
executable: string;
|
||||
args: string[];
|
||||
commandFlag: '-Command' | '-c';
|
||||
preferredLineEnding: LineEnding;
|
||||
pathSeparator: '\\' | '/';
|
||||
};
|
||||
|
||||
// This type configures how static text should be split into lines.
|
||||
export type SplitLinesOptions = {
|
||||
preserveEmptyLines?: boolean;
|
||||
trimTrailingEmptyLine?: boolean;
|
||||
};
|
||||
|
||||
// This type configures how streaming stdout and stderr chunks should be accumulated.
|
||||
export type StreamLineAccumulatorOptions = {
|
||||
preserveEmptyLines?: boolean;
|
||||
};
|
||||
|
||||
// This type is the public contract for incremental line parsing from process streams.
|
||||
export type StreamLineAccumulator = {
|
||||
push: (chunk: Buffer | string) => string[];
|
||||
flush: () => string[];
|
||||
peek: () => string;
|
||||
reset: () => void;
|
||||
};
|
||||
Reference in New Issue
Block a user