Merge branch 'code-cleanup'

# Conflicts:
#	src/parse.ts
#	src/run.ts
#	src/update.ts
This commit is contained in:
Chris Wanstrath 2026-03-13 14:08:37 -07:00
commit 24981c6cc0
12 changed files with 27 additions and 61 deletions

View File

@ -23,6 +23,7 @@ Transcript-based shell integration test runner. Bun + TypeScript.
- `src/match.ts` — wildcard-aware output matching and diff generation
- `src/format.ts` — evaluates pass/fail, formats failures and summary
- `src/update.ts` — rewrites `.shout` files with actual output (`--update` mode)
- `src/utils.ts` — shared utilities (`trimTrailingEmpty`, `escapeRegex`)
- `src/duration.ts` — parses duration strings (`10s`, `500ms`, `1m`)
- `src/cli/index.ts` — CLI entry point via `commander`
- `src/index.ts` — barrel exports

View File

@ -12,8 +12,6 @@
"devDependencies": {
"@types/bun": "latest",
"@types/diff": "^8.0.0",
},
"peerDependencies": {
"typescript": "^5.9.3",
},
},

View File

@ -1 +0,0 @@
console.log("Hello via Bun!");

View File

@ -15,19 +15,12 @@
},
"scripts": {
"check": "bunx tsc --noEmit",
"build": "./scripts/build.sh",
"cli:build": "bun run scripts/build.ts",
"cli:build:all": "bun run scripts/build.ts --all",
"cli:install": "bun cli:build && sudo cp dist/shout /usr/local/bin",
"cli:link": "ln -sf $(pwd)/src/cli/index.ts ~/.bun/bin/shout",
"cli:uninstall": "sudo rm /usr/local/bin",
"test": "bun test"
},
"devDependencies": {
"@types/bun": "latest",
"@types/diff": "^8.0.0"
},
"peerDependencies": {
"@types/diff": "^8.0.0",
"typescript": "^5.9.3"
},
"dependencies": {

View File

@ -139,7 +139,6 @@ program
sourceDir: resolve(dirname(filePath)),
projectDir: cwd,
timeout: timeoutMs,
verbose: opts.verbose ?? false,
onCommand: opts.verbose
? (cmd) => process.stderr.write(ansis.dim(` $ ${cmd.command}\n`))
: undefined,

View File

@ -72,20 +72,21 @@ export function formatFailure(test: TestResult): string {
lines.push(` ${ansis.dim("$")} ${failure.result.command.command}`)
if (failure.diffLines.length > 0) {
lines.push(ansis.red(" expected:"))
const expectedLines: string[] = []
const actualLines: string[] = []
for (const dl of failure.diffLines) {
const text = dl.kind === "context" ? ansis.dim(dl.text) : dl.text
if (dl.kind === "expected" || dl.kind === "equal" || dl.kind === "context") {
const prefix = dl.kind === "expected" ? ansis.red(" > ") : " "
lines.push(`${prefix}${dl.kind === "context" ? ansis.dim(dl.text) : dl.text}`)
expectedLines.push(`${prefix}${text}`)
}
}
lines.push(ansis.green(" actual:"))
for (const dl of failure.diffLines) {
if (dl.kind === "actual" || dl.kind === "equal" || dl.kind === "context") {
const prefix = dl.kind === "actual" ? ansis.green(" > ") : " "
lines.push(`${prefix}${dl.kind === "context" ? ansis.dim(dl.text) : dl.text}`)
actualLines.push(`${prefix}${text}`)
}
}
lines.push(ansis.red(" expected:"), ...expectedLines)
lines.push(ansis.green(" actual:"), ...actualLines)
}
if (failure.exitCodeMismatch) {

View File

@ -1,3 +1,5 @@
import { escapeRegex } from "./utils.ts"
export function matchLine(pattern: string, actual: string): boolean {
if (!pattern.includes("...")) return pattern === actual
@ -8,10 +10,6 @@ export function matchLine(pattern: string, actual: string): boolean {
return regex.test(actual)
}
function escapeRegex(s: string): string {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
}
export function matchOutput(
expected: string[],
actual: string[],

View File

@ -1,3 +1,5 @@
import { trimTrailingEmpty } from "./utils.ts"
export type Command = {
line: number
raw: string
@ -54,12 +56,6 @@ function parseExitCode(lines: string[]): {
return { lines, exitCode: null }
}
function trimTrailingEmpty(lines: string[]): string[] {
let end = lines.length
while (end > 0 && lines[end - 1] === "") end--
return lines.slice(0, end)
}
function parseEnvDirective(path: string, line: string, lineNum: number): { key: string; value: string } {
const rest = line.slice(5).trim()
const eq = rest.indexOf("=")

View File

@ -3,6 +3,7 @@ import { tmpdir } from "node:os"
import { join } from "node:path"
import type { Command, ShoutFile } from "./parse.ts"
import { trimTrailingEmpty } from "./utils.ts"
export type CommandResult = {
command: Command
@ -24,7 +25,6 @@ type RunOptions = {
sourceDir?: string
projectDir?: string
timeout: number
verbose: boolean
onCommand?: (cmd: Command) => void
}
@ -92,7 +92,6 @@ function buildScript(commands: Command[], sentinel: string, verbose: boolean): s
function parseSentinelOutput(
raw: string,
sentinel: string,
commandCount: number,
): { outputs: string[][]; exitCodes: number[] } {
const outputs: string[][] = []
@ -100,7 +99,7 @@ function parseSentinelOutput(
// Split by sentinel lines
const sentinelRegex = new RegExp(
`${escapeRegex(sentinel)}(\\d+)_(\\d+)__`,
`${SENTINEL_PREFIX}(\\d+)_(\\d+)__`,
)
let remaining = raw
@ -152,16 +151,6 @@ function stripAnsi(line: string): string {
return line.replace(ANSI_REGEX, "")
}
function trimTrailingEmpty(lines: string[]): string[] {
let end = lines.length
while (end > 0 && lines[end - 1] === "") end--
return lines.slice(0, end)
}
function escapeRegex(s: string): string {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
}
function streamVerboseMarkers(
stderr: ReadableStream<Uint8Array>,
commands: Command[],
@ -254,7 +243,6 @@ export async function runFile(
const { outputs, exitCodes } = parseSentinelOutput(
stdout,
sentinel,
file.commands.length,
)

View File

@ -1,7 +1,8 @@
import type { CommandResult } from "./run.ts"
import type { ShoutFile } from "./parse.ts"
import { matchOutput, matchLine } from "./match.ts"
import { matchOutput } from "./match.ts"
import { isCommentLine } from "./parse.ts"
import { trimTrailingEmpty } from "./utils.ts"
export function rewriteFile(
file: ShoutFile,
@ -83,9 +84,3 @@ export function rewriteFile(
function escapeDollar(line: string): string {
return line.startsWith("$ ") ? "\\" + line : line
}
function trimTrailingEmpty(lines: string[]): string[] {
let end = lines.length
while (end > 0 && lines[end - 1] === "") end--
return lines.slice(0, end)
}

9
src/utils.ts Normal file
View File

@ -0,0 +1,9 @@
export function trimTrailingEmpty(lines: string[]): string[] {
let end = lines.length
while (end > 0 && lines[end - 1] === "") end--
return lines.slice(0, end)
}
export function escapeRegex(s: string): string {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
}

View File

@ -27,17 +27,6 @@
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false,
"baseUrl": ".",
"paths": {
"$*": [
"./src/server/*"
],
"@*": [
"./src/shared/*"
],
"%*": [
"./src/lib/*"
]
}
"baseUrl": "."
}
}