docs: add @env/@setup directives to CLAUDE.md
This commit is contained in:
parent
70008d16b9
commit
86eba1a624
|
|
@ -6,7 +6,7 @@ Transcript-based shell integration test runner. Bun + TypeScript.
|
||||||
|
|
||||||
- `bun test` — run unit tests
|
- `bun test` — run unit tests
|
||||||
- `bunx tsc --noEmit` — type check
|
- `bunx tsc --noEmit` — type check
|
||||||
- `bun run src/cli/index.ts [files...]` — run shout CLI
|
- `bun run src/cli/index.ts [files...]` — run shout CLI (`--port-from <n>` auto-assigns `$PORT`)
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
|
|
@ -28,6 +28,8 @@ Transcript-based shell integration test runner. Bun + TypeScript.
|
||||||
- `[N]` on last line of expected output = assert exit code N
|
- `[N]` on last line of expected output = assert exit code N
|
||||||
- `[*]` = assert any non-zero exit code; default expects 0
|
- `[*]` = assert any non-zero exit code; default expects 0
|
||||||
- `#` after a command = comment (stripped); `#` in expected output is literal
|
- `#` after a command = comment (stripped); `#` in expected output is literal
|
||||||
|
- `@env KEY=VALUE` before first command = set environment variable
|
||||||
|
- `@setup path.shout` before first command = prepend commands (and `@env`) from another file
|
||||||
- Each file runs in a fresh temp dir with a single `/bin/sh` session
|
- Each file runs in a fresh temp dir with a single `/bin/sh` session
|
||||||
|
|
||||||
## Style
|
## Style
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { resolve, relative, dirname } from "node:path"
|
||||||
import { program } from "commander"
|
import { program } from "commander"
|
||||||
import ansis from "ansis"
|
import ansis from "ansis"
|
||||||
|
|
||||||
import { parse, type ShoutFile } from "../parse.ts"
|
import { parse, type Command, type ShoutFile } from "../parse.ts"
|
||||||
import { runFile, cleanupTmpDir } from "../run.ts"
|
import { runFile, cleanupTmpDir } from "../run.ts"
|
||||||
import { evaluateFile, formatFailure, formatSummary } from "../format.ts"
|
import { evaluateFile, formatFailure, formatSummary } from "../format.ts"
|
||||||
import type { TestResult } from "../format.ts"
|
import type { TestResult } from "../format.ts"
|
||||||
|
|
@ -103,21 +103,19 @@ $ true
|
||||||
if (d.type === "env") envVars[d.key] = d.value
|
if (d.type === "env") envVars[d.key] = d.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve @setup directives: prepend setup commands
|
// Resolve @setup directives: prepend setup commands, collect their @env
|
||||||
let setupCount = 0
|
const setupCommands: Command[] = []
|
||||||
let merged: ShoutFile = parsed
|
for (const d of parsed.directives) {
|
||||||
const setupDirectives = parsed.directives.filter(d => d.type === "setup")
|
if (d.type !== "setup") continue
|
||||||
if (setupDirectives.length > 0) {
|
const setupPath = resolve(dirname(filePath), d.path)
|
||||||
const setupCommands = []
|
const setupContent = await readFile(setupPath, "utf-8")
|
||||||
for (const d of setupDirectives) {
|
const setupParsed = parse(relative(cwd, setupPath), setupContent)
|
||||||
const setupPath = resolve(dirname(filePath), d.path)
|
for (const sd of setupParsed.directives) {
|
||||||
const setupContent = await readFile(setupPath, "utf-8")
|
if (sd.type === "env") envVars[sd.key] = sd.value
|
||||||
const setupParsed = parse(relative(cwd, setupPath), setupContent)
|
|
||||||
setupCommands.push(...setupParsed.commands)
|
|
||||||
}
|
}
|
||||||
setupCount = setupCommands.length
|
setupCommands.push(...setupParsed.commands)
|
||||||
merged = { ...parsed, commands: [...setupCommands, ...parsed.commands] }
|
|
||||||
}
|
}
|
||||||
|
const merged: ShoutFile = { ...parsed, commands: [...setupCommands, ...parsed.commands] }
|
||||||
|
|
||||||
const fileResult = await runFile(merged, {
|
const fileResult = await runFile(merged, {
|
||||||
cleanEnv: opts.cleanEnv ?? false,
|
cleanEnv: opts.cleanEnv ?? false,
|
||||||
|
|
@ -130,15 +128,15 @@ $ true
|
||||||
: undefined,
|
: undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const fileOwnResults = fileResult.results.slice(setupCommands.length)
|
||||||
|
|
||||||
const testResult = evaluateFile(
|
const testResult = evaluateFile(
|
||||||
parsed.path,
|
parsed.path,
|
||||||
fileResult.results,
|
fileOwnResults,
|
||||||
fileResult.error,
|
fileResult.error,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (opts.update && fileResult.results.length > 0) {
|
if (opts.update && fileOwnResults.length > 0) {
|
||||||
// Only rewrite with the file's own results, not setup results
|
|
||||||
const fileOwnResults = fileResult.results.slice(setupCount)
|
|
||||||
const updated = rewriteFile(parsed, fileOwnResults, content)
|
const updated = rewriteFile(parsed, fileOwnResults, content)
|
||||||
if (updated !== content) {
|
if (updated !== content) {
|
||||||
await writeFile(filePath, updated)
|
await writeFile(filePath, updated)
|
||||||
|
|
|
||||||
|
|
@ -109,4 +109,10 @@ describe("parse", () => {
|
||||||
const result = parse("test.shout", "$ echo hi\nhi\n")
|
const result = parse("test.shout", "$ echo hi\nhi\n")
|
||||||
expect(result.directives).toEqual([])
|
expect(result.directives).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("unknown directive throws", () => {
|
||||||
|
expect(() => parse("test.shout", "@evn PORT=3000\n$ echo hi\n")).toThrow(
|
||||||
|
"test.shout:1: unknown directive: @evn PORT=3000",
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,8 @@ export function parse(path: string, content: string): ShoutFile {
|
||||||
if (eq > 0) {
|
if (eq > 0) {
|
||||||
directives.push({ type: "env", key: rest.slice(0, eq), value: rest.slice(eq + 1), line: i + 1 })
|
directives.push({ type: "env", key: rest.slice(0, eq), value: rest.slice(eq + 1), line: i + 1 })
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`${path}:${i + 1}: unknown directive: ${line}`)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user