Compare commits

..

No commits in common. "8c86c852e57da1c116b16d4b535bd556bcc16d5d" and "10e98e2dc5c0d61fe2c48d740d2a991fe7f2307e" have entirely different histories.

2 changed files with 16 additions and 61 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@because/shout", "name": "@because/shout",
"version": "0.0.9", "version": "0.0.8",
"description": "shell output tester", "description": "shell output tester",
"module": "src/index.ts", "module": "src/index.ts",
"type": "module", "type": "module",

View File

@ -29,23 +29,12 @@ type RunOptions = {
} }
const SENTINEL_PREFIX = "__SHOUT_SENTINEL_" const SENTINEL_PREFIX = "__SHOUT_SENTINEL_"
const VERBOSE_MARKER = "__SHOUT_CMD_"
function buildScript(commands: Command[], sentinel: string, verbose: boolean): string { function buildScript(commands: Command[], sentinel: string): string {
const lines: string[] = [] const lines: string[] = ["exec 2>&1"]
if (verbose) {
// Save original stderr to fd 3 before merging stderr into stdout
lines.push("exec 3>&2 2>&1")
} else {
lines.push("exec 2>&1")
}
for (let i = 0; i < commands.length; i++) { for (let i = 0; i < commands.length; i++) {
const cmd = commands[i]! const cmd = commands[i]!
if (verbose) {
lines.push(`printf '${VERBOSE_MARKER}${i}\\n' >&3`)
}
lines.push(cmd.command) lines.push(cmd.command)
// Sentinel: printf to avoid echo interpretation issues // Sentinel: printf to avoid echo interpretation issues
// Format: __SHOUT_SENTINEL_<exitcode>_<index>__ // Format: __SHOUT_SENTINEL_<exitcode>_<index>__
@ -122,36 +111,6 @@ function escapeRegex(s: string): string {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
} }
function streamVerboseMarkers(
stderr: ReadableStream<Uint8Array>,
commands: Command[],
onCommand: (cmd: Command) => void,
): void {
const reader = stderr.getReader()
const decoder = new TextDecoder()
let buffer = ""
const pump = (): void => {
reader.read().then(({ done, value }) => {
if (done) return
if (value) buffer += decoder.decode(value, { stream: true })
let nlIdx: number
while ((nlIdx = buffer.indexOf("\n")) !== -1) {
const line = buffer.slice(0, nlIdx)
buffer = buffer.slice(nlIdx + 1)
if (line.startsWith(VERBOSE_MARKER)) {
const i = parseInt(line.slice(VERBOSE_MARKER.length), 10)
if (i >= 0 && i < commands.length) {
onCommand(commands[i]!)
}
}
}
pump()
}).catch(() => {})
}
pump()
}
export async function runFile( export async function runFile(
file: ShoutFile, file: ShoutFile,
options: RunOptions, options: RunOptions,
@ -163,8 +122,7 @@ export async function runFile(
} }
const sentinel = SENTINEL_PREFIX const sentinel = SENTINEL_PREFIX
const verbose = options.verbose && !!options.onCommand const script = buildScript(file.commands, sentinel)
const script = buildScript(file.commands, sentinel, verbose)
const env: Record<string, string> = options.cleanEnv const env: Record<string, string> = options.cleanEnv
? {} ? {}
@ -197,19 +155,11 @@ export async function runFile(
}) })
try { try {
if (verbose) {
// Stream stderr for verbose command markers before writing script
streamVerboseMarkers(proc.stderr, file.commands, options.onCommand!)
}
proc.stdin.write(script) proc.stdin.write(script)
proc.stdin.end() proc.stdin.end()
const totalTimeout = options.timeout * file.commands.length const stdout = await readWithTimeout(proc.stdout, options.timeout * file.commands.length)
const stdout = await readWithTimeout(proc.stdout, totalTimeout) const stderr = await readWithTimeout(proc.stderr, 1000).catch(() => "")
if (!verbose) {
await readWithTimeout(proc.stderr, 1000).catch(() => "")
}
await proc.exited await proc.exited
@ -219,11 +169,16 @@ export async function runFile(
file.commands.length, file.commands.length,
) )
const results: CommandResult[] = file.commands.map((cmd, i) => ({ const results: CommandResult[] = file.commands.map((cmd, i) => {
if (options.verbose && options.onCommand) {
options.onCommand(cmd)
}
return {
command: cmd, command: cmd,
actual: outputs[i] ?? [], actual: outputs[i] ?? [],
exitCode: exitCodes[i] ?? 1, exitCode: exitCodes[i] ?? 1,
})) }
})
return { file, results, tmpDir } return { file, results, tmpDir }
} catch (err) { } catch (err) {