mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-14 19:27:07 -04:00
fix(cli): --help prints usage to stdout and exits 0 (#254)
Asking for help is not an error, but `designcraft-cli --help` shared the catch-all arm with unknown commands, so it wrote the usage block to stderr and exited 1 — unusable in a pipeline and wrong by convention (`--version` already exits 0). Split the arm: `--help`, `-h` and `help` now print the same usage block to stdout and succeed, while an unknown command keeps it on stderr and still fails. The usage text and links line are hoisted into a shared `USAGE` const and `usage_footer()` so the two paths can't drift. Fixes #243 🤖 Generated with Codebuff Co-authored-by: Codebuff <[email protected]> Co-authored-by: Brandon Thomas <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2 files changed
+42
-9
No files matched your search
@@ -34,6 +34,19 @@ mod perf;
|
||||
use designcraft_engine::Session;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
/// The usage block. Shared by `--help` (stdout, success) and an unknown command (stderr, failure).
|
||||
const USAGE: &str = "usage: designcraft-cli run [--in FILE | --sample] [--cmd ID[=JSON]]... [--page N] [--scale S] [--pdf-options JSON] [--export OUT] [--all-pages DIR]\n (--page, --scale and --pdf-options apply to the exports that follow them)\n designcraft-cli commands [FILTER]\n designcraft-cli describe COMMAND\n designcraft-cli script [FILE|-] [--in FILE | --sample] [--connect PORT] [--save OUT] [--export OUT] [--keep-going]\n designcraft-cli app [--port PORT] COMMAND [JSON] | --method METHOD [JSON]\n designcraft-cli mcp [--connect PORT] [--sample]\n designcraft-cli perf [--pages N] [--runs N] [--strict]\n designcraft-cli bench FILE [--runs N]\n designcraft-cli links\n designcraft-cli --version";
|
||||
|
||||
/// The links line under the usage block.
|
||||
fn usage_footer() -> String {
|
||||
format!(
|
||||
"\nCommunity: {} · {} · {}",
|
||||
designcraft_engine::links::DISCORD,
|
||||
designcraft_engine::links::APP_PAGE,
|
||||
designcraft_engine::links::GITHUB
|
||||
)
|
||||
}
|
||||
|
||||
/// stdout went away. A reader that stopped early (a closed pipe) ends the program quietly, as
|
||||
/// ripgrep does; any other write error is reported.
|
||||
fn stdout_failed(e: std::io::Error) -> ! {
|
||||
@@ -76,16 +89,16 @@ fn main() -> ExitCode {
|
||||
outln!("Discord {DISCORD}\nWebsite {WEBSITE}\nApp page {APP_PAGE}\nGitHub {GITHUB}\nIssues {ISSUES}");
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
// Asking for help is not an error: it goes to stdout and succeeds. An unknown command
|
||||
// still prints the same usage to stderr and fails.
|
||||
Some("--help" | "-h" | "help") => {
|
||||
outln!("{USAGE}");
|
||||
outln!("{}", usage_footer());
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
_ => {
|
||||
eprintln!(
|
||||
"usage: designcraft-cli run [--in FILE | --sample] [--cmd ID[=JSON]]... [--page N] [--scale S] [--pdf-options JSON] [--export OUT] [--all-pages DIR]\n (--page, --scale and --pdf-options apply to the exports that follow them)\n designcraft-cli commands [FILTER]\n designcraft-cli describe COMMAND\n designcraft-cli script [FILE|-] [--in FILE | --sample] [--connect PORT] [--save OUT] [--export OUT] [--keep-going]\n designcraft-cli app [--port PORT] COMMAND [JSON] | --method METHOD [JSON]\n designcraft-cli mcp [--connect PORT] [--sample]\n designcraft-cli perf [--pages N] [--runs N] [--strict]\n designcraft-cli bench FILE [--runs N]\n designcraft-cli links\n designcraft-cli --version"
|
||||
);
|
||||
eprintln!(
|
||||
"\nCommunity: {} · {} · {}",
|
||||
designcraft_engine::links::DISCORD,
|
||||
designcraft_engine::links::APP_PAGE,
|
||||
designcraft_engine::links::GITHUB
|
||||
);
|
||||
eprintln!("{USAGE}");
|
||||
eprintln!("{}", usage_footer());
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,26 @@ fn run_cmd_references_and_describe() {
|
||||
assert!(v.as_array().unwrap().iter().all(|c| c.to_string().contains("footnote")));
|
||||
}
|
||||
|
||||
/// `designcraft-cli --help` printed usage to stderr and exited 1; asking for help is not an
|
||||
/// error (#243). It now goes to stdout and succeeds, while an unknown command keeps the same
|
||||
/// usage on stderr and fails.
|
||||
#[test]
|
||||
fn help_goes_to_stdout_and_unknown_args_to_stderr() {
|
||||
for flag in ["--help", "-h", "help"] {
|
||||
let out = cli().arg(flag).output().unwrap();
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
assert!(out.status.success(), "{flag}: {:?}", out.status);
|
||||
assert!(stdout.starts_with("usage: designcraft-cli"), "{flag}: {stdout:?}");
|
||||
assert!(stdout.contains("designcraft-cli --version"), "{flag}: {stdout:?}");
|
||||
assert!(out.stderr.is_empty(), "{flag} wrote to stderr: {}", String::from_utf8_lossy(&out.stderr));
|
||||
}
|
||||
// An unknown option is still an error: usage on stderr, nothing on stdout, non-zero exit.
|
||||
let out = cli().arg("--no-such-option").output().unwrap();
|
||||
assert!(!out.status.success(), "{:?}", out.status);
|
||||
assert!(out.stdout.is_empty(), "unexpected stdout: {}", String::from_utf8_lossy(&out.stdout));
|
||||
assert!(String::from_utf8_lossy(&out.stderr).starts_with("usage: designcraft-cli"));
|
||||
}
|
||||
|
||||
/// `designcraft-cli commands | head -1` panicked with "failed printing to
|
||||
/// stdout: Broken pipe (os error 32)" and exit status 101.
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user