After writing out some script stuff, I'm wondering if we should change the API for basic-cli Cmd, and combine the args into the new call. We would get rid of the Cmd.arg and Cmd.args functions as they wouldn't be needed.
# how about this
Cmd.new ["roc", "www/main.roc", "--", "www/content/", "www/dist/"]
# instead of this
Cmd.new "roc"
|> Cmd.args ["www/main.roc", "--", "www/content/", "www/dist/"]
Is there any reason we would want to keep these separate instead?
Maybe we have a Cmd that we later apply new arguments to before executing? but in this case is it really that much worse than just building from scratch?
I like that idea! :thumbs_up:
The reason they are separate in rust is to make it easier to build up programmatically with constants and some conditionals. Instead of requiring building the list fully first.
Don't have a major preference either way, just noting the logic
yeah I just kinda figure you could build up the list the same way first haha
For sure. I wonder if there is any other benefit I am missing. Probably not, just a nice builder API.
Oh, I guess if you ever expand part just args, there might be reason to build it on the command struct
Like env and what not
Instead of building n separate lists
Does this look ok? We introduce a possible invalid state where an empty list is passed in.
I think this is acceptable, as it does nothing, but I guess we could crash.
new : List Str -> Cmd
new = \programAndArgs ->
(program, args) =
when programAndArgs is
[] -> ("",[])
[p] -> (p,[])
[p,.. as a] -> (p,a)
@Cmd {
program,
args,
envs: [],
clearEnvs: Bool.false,
}
does an empty program name make any sense?
that must actually be the reason right for the rust api: that you must specify a program name
of course I could also specify the program name as "flkjf3rkejfkefej" and that would fail because it does not exist, but at least taking the program name separately is more explicit
well you can already pass in "" for the program name, so I'm not sure that's significantly better haha
that would look weird
while giving a program an empty list of arguments is fine
that's fair
How about making new accept a Str for the program name and a separate list of args? That way it would be more clear that the program jam is required
After thinking on this, I think we should stick with the current API for a little longer.
With the new syntax, it will be nicer I think to write scripts, and so we should have more real experiences to work with.
I'd like to revisit this idea in a few months.
I was playing a little and came up with:
cmd = \app, args, errMapper ->
Cmd.new app |> Cmd.args args |> Cmd.status |> Task.mapErr errMapper
removeDir = \path ->
verifyDirExists! path
line! "Removing files from $(path)..."
cmd "rm" ["-rf", path] ErrRemovingDir
copyFiles = \from, to ->
verifyDirExists! from
line! "Copying files from $(from) to $(to)..."
cmd "cp" ["-r", from, to] ErrCopyingFiles
generateSiteContent = \path ->
verifyDirExists! path
line! "Generating static site..."
cmd "roc" ["www/main.roc", "--", "www/content/", "www/dist/"] ErrBuildingSite
serveFiles =
line! "Serving static site..."
cmd "simple-http-server" ["-p", "8080", "--nocache", "--cors", "--index", "--", "www/dist/"] ErrServingFiles
I think it is good enough at this point not to change the official API. It's just a one/two lines and everyone can adjust to their own requirements on the spot.
I'm actually in favor of changing it :big_smile:
the API we're using is actually pretty unusual for a language like Roc
the List Str approach is much more common, and I think the Rust API that ours is based on is kind of an outlier
I think it's worth switching to try doing it the more common way!
I am still a little bit confused: does the Cmd API belong to basic-cli just for now, or is it going to stay that way? In other words, do we want to create a standard for the CLI-like platforms?
Once module params land, it could become generic and shared, but I assume it will stick in just basic CLI for a while.
We haven't been taling about envrionment variables, or the other configuration options though.
Another idea I had was accepting a Str for the program name, and then a record with the rest of the configuration options.
That sound really nice with optional params
personally I think for the shell scripting use case (e.g. rewriting our build script for roc-lang.org from Bash to Roc), it would be very convenient to have something like:
exec : Str, List Str -> Task Output ExecErr
execWith : Str, { args ? List Str, ...etc } -> Task Output ExecErr
So there is a key difference between output and status.
output doens't inherit stdio from it's parent, instead Output is captured and returned as a {stdout : List U8, stderr : List U8} status inherits stdio from it's parent, so it returns {} As a helper for scripts I have implement the API as Cmd.exec : Str, List Str -> Task {} [Error] in #184 which behaves the same as status and inherits from the process executing the roc script.
Happy to change it again in a later PR.
Last updated: Jun 16 2026 at 16:19 UTC