feat!: consolidate cot cli commands into one - #587
Conversation
|
| Project | cot |
| Branch | elijah/cot-proxy-cmd |
| Testbed | github-ubuntu-latest |
🚨 1 Alert
| Benchmark | Measure Units | View | Benchmark Result (Result Δ%) | Upper Boundary (Limit %) |
|---|---|---|---|---|
| empty_router/empty_router | Latency milliseconds (ms) | 📈 plot 🚷 threshold 🚨 alert (🔔) | 13.67 ms(+88.48%)Baseline: 7.25 ms | 12.55 ms (108.92%) |
Click to view all benchmark results
| Benchmark | Latency | Benchmark Result milliseconds (ms) (Result Δ%) | Upper Boundary milliseconds (ms) (Limit %) |
|---|---|---|---|
| empty_router/empty_router | 📈 view plot 🚷 view threshold 🚨 view alert (🔔) | 13.67 ms(+88.48%)Baseline: 7.25 ms | 12.55 ms (108.92%) |
| json_api/json_api | 📈 view plot 🚷 view threshold | 1.13 ms(+4.61%)Baseline: 1.08 ms | 1.37 ms (82.69%) |
| nested_routers/nested_routers | 📈 view plot 🚷 view threshold | 1.05 ms(+4.51%)Baseline: 1.01 ms | 1.24 ms (84.58%) |
| single_root_route/single_root_route | 📈 view plot 🚷 view threshold | 1.02 ms(+4.72%)Baseline: 0.97 ms | 1.21 ms (84.17%) |
| single_root_route_burst/single_root_route_burst | 📈 view plot 🚷 view threshold | 20.14 ms(+15.91%)Baseline: 17.37 ms | 21.69 ms (92.85%) |
- help for workspaces and packages now dispatch to the custom help handler
Codecov Report❌ Patch coverage is
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
| #[cfg(unix)] | ||
| { | ||
| let err = std::process::Command::new(&proj.path).args(args).exec(); | ||
| anyhow::bail!("Failed to exec {}: {err}", proj.path.display()); | ||
| } | ||
|
|
||
| #[cfg(not(unix))] | ||
| { | ||
| let status = std::process::Command::new(&proj.path).args(args).status()?; | ||
| std::process::exit(status.code().unwrap_or(1)); | ||
| } |
There was a problem hiding this comment.
Where's the difference between Unix-like and non-Unix-like platforms coming from? Why do we need separate code paths here?
There was a problem hiding this comment.
Do we need all this machinery here? Any chance that running cargo run would suffice instead?
| tracing.workspace = true | ||
| tracing-subscriber = { workspace = true, features = ["env-filter"] } | ||
| serde = { workspace = true, features = ["derive"] } | ||
| serde_json = {workspace = true} |
There was a problem hiding this comment.
nit
| serde_json = {workspace = true} | |
| serde_json = { workspace = true } |
|
|
||
| /// Metadata describing the commands exposed by a Cot project binary. | ||
| #[derive(Debug, Clone, Deserialize, Serialize)] | ||
| pub struct ProjectMetadata { |
There was a problem hiding this comment.
If we define some custom metadata format, we should add some versioning here to make sure that the versions of cot-cli and metadata's match. Note that this can just be an integer increased each time any of the field change; this doesn't necessarily have to be the cot version itself.
| -q, --quiet... Decrease logging verbosity | ||
| -h, --help Print help | ||
| -V, --version Print version | ||
| --release |
There was a problem hiding this comment.
Why did the formatting change so much here? IS this a result of adding a long param description, or something else?
| use serde::{Deserialize, Serialize}; | ||
|
|
||
| /// Flag used to ask a Cot project binary to print its CLI metadata as JSON. | ||
| pub const METADATA_FLAG: &str = "--metadata"; |
There was a problem hiding this comment.
I think this option should be renamed to something that is unlikely to conflict with something that could be defined by the user. Maybe something more in the line of --cot-internal-cli-metadata?
| bail!(msg); | ||
| } | ||
|
|
||
| let metadata: ProjectMetadata = serde_json::from_slice(&output.stdout).with_context(|| { |
There was a problem hiding this comment.
I don't think this is the right thing to do. Somebody could create a Cot binary without the entire metadata machinery - I'm doing something like this in this project, where the Cot server is only a small part inside a bigger binary, and I don't use any of the typical Cot machinery to run the server.
I don't think cot-cli should fail in case it cannot extract the metadata from the binary. Maybe we could just display an error message, but proceed as normal with the rest of the binary operation?
I'm also wondering if it won't be surprising for the users if the cot-cli actually runs the project binary when attempting to use unknown subcommands. What do you think?
|
|
||
| let output = std::process::Command::new(binary_path) | ||
| .arg(METADATA_FLAG) | ||
| .output() |
There was a problem hiding this comment.
We should probably have some timeout to run the binary to avoid awkward silence when the binary doesn't actually recognize the metadata parameter, or hang for any other reason. This would be a very bad UX for cot-cli.
Background
cotcurrently exposes CLI commands through two separate entry points:cot-clicrate (cot <command>): handles project scaffolding, migration listing, migration generation, and shell completions.check, running migrations (which is also triggered implicitly at startup), and any custom user-defined task commands.In addition, running and building a
cotapp relies on Cargo tooling. In summary, there are three ways to invoke CLI commands today:cot <command>via thecot-clicratecot-cliThis PR focuses on unifying 1 and 2 for ergonomics:
cotnow acts as the single entry point for all commands, proxying any unrecognized command to the compiled binary if it exists there. Option 3 (Cargo invocation) is out of scope for this PR and can be revisited in a follow-up if proxying those commands makes sense.Approach
When
cotreceives a command it does not recognize, it resolves the target binary (target/debugby default, ortarget/releaseif--releaseis passed), queries it for its available commands via a metadata flag, and either forwards the command along with all provided arguments or returns an error if the command is not found in the binary either.Metadata
To support proxying, the
cotcrate exposes a--metadataflag. At runtime, the binary uses reflection to enumerate all registered CLI commands and prints them as JSON to stdout. This serves two purposes: it tellscot-cliwhether a given command should be forwarded, and it provides the information needed to render accurate help text.Example:
Caching
Querying the binary on every invocation would be wasteful, so the metadata response is cached in
.cot/command-cache.json. The cache stores the binary's modified time (mtime) alongside the metadata and is invalidated automatically whenever the binary is rebuilt.Workspaces
When working inside a Cargo workspace, a
--package(-p) flag must be provided to specify which package's binary should be targeted.Type of change