Files
Maximilian Roos c9cfc1ef16 feat(eval): add --format=json for structured template output (#3106)
Adds `--format=json` to `wt step eval`, the structured counterpart to its `-v` view.

`wt step eval` printed only the bare rendered result to stdout; the template's name, source, and rendered result were reachable solely from the `-v` human lane on stderr, with no machine-readable form. `--format=json` emits `{name, template, result}` to stdout — the structured analog of the `-v` expansion view, and consistent with `wt hook show --format=json` and the other `wt step` JSON lanes.

Text mode is unchanged (bare result to stdout). The two lanes compose: `--format=json -v` keeps JSON on stdout and the human expansion view on stderr, so `wt step eval --format=json -v … 2>/dev/null` yields clean JSON.

Implementation mirrors the sibling `wt step` commands: a `SwitchFormat` flag under the `Automation` heading, an inline `serde_json::json!` payload, and snapshot tests covering the JSON lane and the JSON + `-v` composition.

> _This was written by Claude Code on behalf of max_
2026-06-17 10:42:24 -07:00

158 lines
3.7 KiB
Rust

//! Integration tests for `wt step eval`
use crate::common::{TestRepo, make_snapshot_cmd, make_snapshot_cmd_with_global_flags, repo};
use insta_cmd::assert_cmd_snapshot;
use rstest::rstest;
#[rstest]
fn test_eval_branch(repo: TestRepo) {
assert_cmd_snapshot!(make_snapshot_cmd(
&repo,
"step",
&["eval", "{{ branch }}"],
None,
));
}
#[rstest]
fn test_eval_hash_port(repo: TestRepo) {
assert_cmd_snapshot!(make_snapshot_cmd(
&repo,
"step",
&["eval", "{{ branch | hash_port }}"],
None,
));
}
#[rstest]
fn test_eval_multiple_values(repo: TestRepo) {
assert_cmd_snapshot!(make_snapshot_cmd(
&repo,
"step",
&[
"eval",
"{{ branch | hash_port }},{{ (\"supabase-api-\" ~ branch) | hash_port }}"
],
None,
));
}
#[rstest]
fn test_eval_sanitize_db(repo: TestRepo) {
assert_cmd_snapshot!(make_snapshot_cmd(
&repo,
"step",
&["eval", "{{ branch | sanitize_db }}"],
None,
));
}
#[rstest]
fn test_eval_template_error(repo: TestRepo) {
assert_cmd_snapshot!(make_snapshot_cmd(
&repo,
"step",
&["eval", "{{ undefined_var }}"],
None,
));
}
#[rstest]
fn test_eval_verbose(repo: TestRepo) {
assert_cmd_snapshot!(make_snapshot_cmd_with_global_flags(
&repo,
"step",
&["eval", "{{ branch | hash_port }}"],
None,
&["--verbose"],
));
}
#[rstest]
fn test_eval_format_json(repo: TestRepo) {
assert_cmd_snapshot!(make_snapshot_cmd(
&repo,
"step",
&["eval", "--format=json", "{{ branch | hash_port }}"],
None,
));
}
/// `--format=json` and `-v` compose: JSON to stdout, the human expansion view
/// to stderr.
#[rstest]
fn test_eval_format_json_verbose(repo: TestRepo) {
assert_cmd_snapshot!(make_snapshot_cmd_with_global_flags(
&repo,
"step",
&["eval", "--format=json", "{{ branch | hash_port }}"],
None,
&["--verbose"],
));
}
#[rstest]
fn test_eval_owner(repo: TestRepo) {
repo.run_git(&[
"remote",
"set-url",
"origin",
"git@github.com:max-sixty/worktrunk.git",
]);
let output = repo
.wt_command()
.args(["step", "eval", "{{ owner }}/{{ repo }}"])
.output()
.expect("Failed to run wt step eval");
assert!(
output.status.success(),
"wt step eval should succeed, stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
assert_eq!(
String::from_utf8_lossy(&output.stdout).trim(),
"max-sixty/repo"
);
}
#[rstest]
fn test_eval_conditional(repo: TestRepo) {
assert_cmd_snapshot!(make_snapshot_cmd(
&repo,
"step",
&[
"eval",
"{% if branch == 'main' %}production{% else %}development{% endif %}"
],
None,
));
}
/// `{{ commit }}` resolves to the running worktree's HEAD SHA on the on-branch
/// hot path. `build_hook_context` resolves it via `git rev-parse <branch>`
/// (always fresh from the ref store), so the value tracks any HEAD movement
/// during the command.
#[rstest]
fn test_eval_commit_matches_head_sha(repo: TestRepo) {
let expected = repo.git_output(&["rev-parse", "HEAD"]);
let output = repo
.wt_command()
.args(["step", "eval", "{{ commit }}"])
.output()
.expect("run wt step eval");
assert!(
output.status.success(),
"wt step eval failed: stderr={}",
String::from_utf8_lossy(&output.stderr),
);
assert_eq!(
String::from_utf8_lossy(&output.stdout).trim(),
expected,
"eval commit should match HEAD SHA"
);
}