Fix setup env precedence and validate directives
This commit is contained in:
parent
86eba1a624
commit
e97be11a0c
|
|
@ -90,37 +90,46 @@ $ true
|
||||||
const results: TestResult[] = []
|
const results: TestResult[] = []
|
||||||
const cwd = process.cwd()
|
const cwd = process.cwd()
|
||||||
const portFrom = opts.portFrom ? parseInt(opts.portFrom, 10) : undefined
|
const portFrom = opts.portFrom ? parseInt(opts.portFrom, 10) : undefined
|
||||||
|
if (portFrom !== undefined && Number.isNaN(portFrom)) {
|
||||||
|
console.error("--port-from must be an integer")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
let nextPort = portFrom ?? 0
|
let nextPort = portFrom ?? 0
|
||||||
|
|
||||||
const runOne = async (filePath: string, port?: number) => {
|
const runOne = async (filePath: string) => {
|
||||||
const content = await readFile(filePath, "utf-8")
|
const content = await readFile(filePath, "utf-8")
|
||||||
const parsed = parse(relative(cwd, filePath), content)
|
const parsed = parse(relative(cwd, filePath), content)
|
||||||
|
|
||||||
// Collect env vars: --port-from, then @env directives
|
// Resolve directives in a single pass. Setup @env is collected separately
|
||||||
|
// so that the user file's @env always takes precedence.
|
||||||
const envVars: Record<string, string> = {}
|
const envVars: Record<string, string> = {}
|
||||||
if (port !== undefined) envVars["PORT"] = String(port)
|
if (portFrom !== undefined) envVars["PORT"] = String(nextPort++)
|
||||||
for (const d of parsed.directives) {
|
const setupEnvVars: Record<string, string> = {}
|
||||||
if (d.type === "env") envVars[d.key] = d.value
|
const userEnvVars: Record<string, string> = {}
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve @setup directives: prepend setup commands, collect their @env
|
|
||||||
const setupCommands: Command[] = []
|
const setupCommands: Command[] = []
|
||||||
for (const d of parsed.directives) {
|
for (const d of parsed.directives) {
|
||||||
if (d.type !== "setup") continue
|
if (d.type === "setup") {
|
||||||
const setupPath = resolve(dirname(filePath), d.path)
|
const setupPath = resolve(dirname(filePath), d.path)
|
||||||
const setupContent = await readFile(setupPath, "utf-8")
|
const setupContent = await readFile(setupPath, "utf-8")
|
||||||
const setupParsed = parse(relative(cwd, setupPath), setupContent)
|
const setupParsed = parse(relative(cwd, setupPath), setupContent)
|
||||||
for (const sd of setupParsed.directives) {
|
for (const sd of setupParsed.directives) {
|
||||||
if (sd.type === "env") envVars[sd.key] = sd.value
|
if (sd.type === "setup") {
|
||||||
|
throw new Error(`${relative(cwd, setupPath)}: @setup not allowed in setup files`)
|
||||||
|
}
|
||||||
|
if (sd.type === "env") setupEnvVars[sd.key] = sd.value
|
||||||
}
|
}
|
||||||
setupCommands.push(...setupParsed.commands)
|
setupCommands.push(...setupParsed.commands)
|
||||||
|
} else if (d.type === "env") {
|
||||||
|
userEnvVars[d.key] = d.value
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
Object.assign(envVars, setupEnvVars, userEnvVars)
|
||||||
const merged: ShoutFile = { ...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,
|
||||||
pathDirs: opts.path,
|
pathDirs: opts.path,
|
||||||
envVars: Object.keys(envVars).length > 0 ? envVars : undefined,
|
envVars,
|
||||||
timeout: timeoutMs,
|
timeout: timeoutMs,
|
||||||
verbose: opts.verbose ?? false,
|
verbose: opts.verbose ?? false,
|
||||||
onCommand: opts.verbose
|
onCommand: opts.verbose
|
||||||
|
|
@ -128,6 +137,18 @@ $ true
|
||||||
: undefined,
|
: undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Check setup commands for failures
|
||||||
|
for (let i = 0; i < setupCommands.length; i++) {
|
||||||
|
const r = fileResult.results[i]
|
||||||
|
if (r && r.exitCode !== 0) {
|
||||||
|
return evaluateFile(
|
||||||
|
parsed.path,
|
||||||
|
[],
|
||||||
|
`setup command failed (exit ${r.exitCode}): $ ${setupCommands[i]!.command}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const fileOwnResults = fileResult.results.slice(setupCommands.length)
|
const fileOwnResults = fileResult.results.slice(setupCommands.length)
|
||||||
|
|
||||||
const testResult = evaluateFile(
|
const testResult = evaluateFile(
|
||||||
|
|
@ -167,10 +188,7 @@ $ true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.parallel) {
|
if (opts.parallel) {
|
||||||
const tasks = files.map(f => {
|
const tasks = files.map(f => runOne(f))
|
||||||
const port = portFrom !== undefined ? nextPort++ : undefined
|
|
||||||
return runOne(f, port)
|
|
||||||
})
|
|
||||||
const all = await Promise.all(tasks)
|
const all = await Promise.all(tasks)
|
||||||
for (const r of all) {
|
for (const r of all) {
|
||||||
printDots(r)
|
printDots(r)
|
||||||
|
|
@ -179,8 +197,7 @@ $ true
|
||||||
process.stdout.write("\n")
|
process.stdout.write("\n")
|
||||||
} else {
|
} else {
|
||||||
for (const filePath of files) {
|
for (const filePath of files) {
|
||||||
const port = portFrom !== undefined ? nextPort++ : undefined
|
const r = await runOne(filePath)
|
||||||
const r = await runOne(filePath, port)
|
|
||||||
printDots(r)
|
printDots(r)
|
||||||
results.push(r)
|
results.push(r)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,9 +76,10 @@ export function parse(path: string, content: string): ShoutFile {
|
||||||
} else if (line.startsWith("@env ")) {
|
} else if (line.startsWith("@env ")) {
|
||||||
const rest = line.slice(5).trim()
|
const rest = line.slice(5).trim()
|
||||||
const eq = rest.indexOf("=")
|
const eq = rest.indexOf("=")
|
||||||
if (eq > 0) {
|
if (eq <= 0) {
|
||||||
directives.push({ type: "env", key: rest.slice(0, eq), value: rest.slice(eq + 1), line: i + 1 })
|
throw new Error(`${path}:${i + 1}: malformed @env directive (expected KEY=VALUE): ${line}`)
|
||||||
}
|
}
|
||||||
|
directives.push({ type: "env", key: rest.slice(0, eq), value: rest.slice(eq + 1), line: i + 1 })
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`${path}:${i + 1}: unknown directive: ${line}`)
|
throw new Error(`${path}:${i + 1}: unknown directive: ${line}`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user