I started creating a CLI tool in deno like the below:
#!/usr/bin/env deno --allow-run
import { parse } from "https://deno.land/x/[email protected]/mod.ts"
import { exit, args } from "deno"
const main = async (opts) => {
if (opts.help) {
console.log(`Usage: my-command`)
exit(0)
}
console.log('something')
}
main(parse(args.slice(1)))
When I pass --help option to this script, deno's help message was printed like the below:
$ deno example.ts
something
$ deno example.ts --help
Usage: deno script.ts
Options:
--allow-write Allow file system write access.
--allow-net Allow network access.
--allow-env Allow environment access.
--allow-run Allow running subprocesses.
-A, --allow-all Allow all permissions
...
This means my tool is unable to have --help option and this is inconvenient.
How do other CLI tools handle this, like Node.js?
@kitsonk @kt3k I believe this is what we are expecting here: https://docs.rs/getopts/0.2.18/getopts/enum.ParsingStyle.html#variant.StopAtFirstFree
--help is special: I believe some of our customized code (to disambiguate help for deno and for v8) makes things a bit more complicated...
A proposal about flags:
Mixing deno and v8 flags is quite confusing. Since v8 flags are used less often than other deno flags, we could choose to prefix all v8 flags with v8, e.g. --expose-gc becomes --v8-expose-gc.
In Node, any flags/arguments that come after the user's script _belong_ to the user's script.
# foo's help is shown, assuming foo.js handles `--help` in some way
$ node foo.js --help
# Node's help is shown and foo.js is ignored
$ node --help foo.js
That works pretty well in my experience and I would say Deno should behave the same.
@sholladay Yeah that corresponds to StopAtFirstFree parsing style. Other flags at this moment follows this design well, only that --help is processed in a slightly different and problematic way: there is a collision between deno's help flag and that of v8.
For others who want to look into the problem: Node implements a much more complicated custom resolution logic themselves.
The problem we have here is that we want to distinguish v8 specific flags from deno ones: currently we first forward possible flags to v8 in v8_set_flags() and let v8 to remove ones it recognizes, and then we process with getopts in set_recognized_flags(). A few conflicts rises during this process:
deno example.ts --gc-stats and you'll see v8 specific error message.ParsingStyle::StopAtFirstFree to make it stop parsing when meeting with the first free arg (which is the filename) and happily forward the remaining flags after this free arg to the user. However, this means it would not recognize v8 specific flags occurring before the first free arg, unless we explicitly add them as options on our getopts::Options instance.As a side note, the current custom solution of Node seems not perfect either: for example, Node would display v8 error if you do node --gc-stats 1 example.js instead of node --gc-stats=1 example.js. However, since deno currently forward the args first to v8, so deno --gc-stats 1 example.ts works just fine.
Please check out this PR: https://github.com/denoland/deno/pull/1199/files
In Node, any flags/arguments that come after the user's script belong to the user's script.
I'm against this for the following reasons:
I think we can satisfy my desires and support --help in user scripts with some one-off logic: if a script is specified in the args, pass the --help arg through.
Maybe this custom logic ends up being more complex for users than just doing things as Node does them (or using --). I'm willing to concede that if we continue to run into issues - but at the moment I'd like to try a bit harder to support CLI args in any position.
@kevinkassimo regarding V8 flags - I think it's a separate issue?
@ry My only concern about this design is that it would render attempts to use shebang that hides away deno broken... considering especially flags like -A, -D, etc. common. Breaks transparency.
(sidenote: we currently allow extensionless script with shebang run just fine with deno using .mime files)
V8 flags sound indeed like a separate issue. I brought it up here simply because our current logic of handling it seems "weird" and causes troubles if I want to change the flag code even a little bit.
My only concern about this design is that it would render attempts to use shebang that hides away deno broken... considering especially flags like -A, -D, etc. common. Breaks transparency.
@kevinkassimo I don't think I follow. Can you give an example?
@ry consider a script shebang (no extension):
#!/usr/bin/env deno
import * as deno from "deno";
console.log(deno.args)
with shebang.mime:
text/typescript
Run ./shebang -A, emits:
[ "./shebang" ]
-A is gone due to our flag processing logic. Would surprise users.
how about using --? that would potentially solve the issue when it matters (i.e. shebang case)
#!/usr/bin/env deno --allow-all --
the above shebang would pass specific arguments to deno while passing everything else to the program, and it's optional so users wanting to add deno arguments at the end would be able to do so.
Edit:
For this to work, the first argument would need to come after the --:
deno -A -- program.ts -A
would pass -A to both the program and to Deno for example
This should be possible now with the clap-based flag parsing.
Most helpful comment
In Node, any flags/arguments that come after the user's script _belong_ to the user's script.
That works pretty well in my experience and I would say Deno should behave the same.