cacjs / cac Goto Github PK
View Code? Open in Web Editor NEWSimple yet powerful framework for building command-line apps.
License: MIT License
Simple yet powerful framework for building command-line apps.
License: MIT License
ESM support so we can do this:
import cac from 'cac'
const cli = cac()
// ...
No ESM support for Node.js
Adding these fields to package.json
"type": "module",
"module: "...",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./package.json": "./package.json",
"./": "./"
}
Note that CJS will still work as before, here's an example of such module: https://github.com/talentlessguy/es-mime-types
Because this project already uses Rollup, it will be very easy to add a module target.
if needed, I can make a pull request
cli.outputHelp()
, that I can then set the exit code (e.g. to 1).cli.outputHelp()
calls process.exit(0)
for me.cli.outputHelp()
process.exit(0)
in cli.outputHelp()
(breaking change, though)It would be cool to have auto-complete in the terminal for different arguments. Not sure how possible it is, but this library seems to implement the feature:
Keywords: options, alias, aliases, hyphen, kebab-case
Long hyphenated options (e.g. --clear-screen
) aren't recognised as aliases of short options (e.g. -c
) or long non-hyphenated options (e.g. --clear
).
Suppose you have a --clear-screen
option (as in the README) which can also be called as --clear
or -c
:
cat ./test.js
const cli = require('cac')()
cli.option('-c, --clear, --clear-screen')
console.log(cli.parse().options)
If this command is called with -c
or --clear
(or --clearScreen
), all three aliases are assigned the value, e.g.:
node ./test.js -c # { '--': [], c: true, clear: true, clearScreen: true }
node ./test.js --clear # { '--': [], c: true, clear: true, clearScreen: true }
node ./test.js --clearScreen # { '--': [], c: true, clear: true, clearScreen: true }
But if the command is called with the long hyphenated option, it's treated as an unknown option (the aliases are not assigned the value):
node ./test.js --clear-screen # { '--': [], clearScreen: true }
node ./test.js --unknown-option # { '--': [], unknownOption: true }
CAC: 6.7.3
Node: v14.17.3
OS: Linux (Arch)
cac
is going to be like commander.js
more while keeping the good part of minimist
.
const cac = require('cac')
const cli = cac()
// ...
cli.parse()
// cli.js
cli.command('init', 'description', (input, flags) => {})
// or omit the handler function to use an external file
cli.command('init', 'description')
// then we get the handler function by `require('./cli-init')`
// cli.js
cli.option('w, watch', 'description')
// this will be included i top-level command's help
// eg: `node cli.js --help`
cli.option('w, watch', 'description', {command: 'init'})
// this will be included in subcommand `init`'s help
// eg: `node cli.js init --help`
// test.js
const cli = require('cac')();
cli.option('--no-aaa-bbb').option('--no-ccc');
const parsed = cli.parse();
console.log(JSON.stringify(parsed, null, 2));
$ node test.js
{
"args": [],
"options": {
"--": [],
"aaa-bbb": true,
"ccc": true
}
}
$ node test.js --no-aaa-bbb
{
"args": [],
"options": {
"--": [],
"aaa-bbb": true,
"ccc": true,
"aaaBbb": false
}
}
$ node test.js --no-ccc
{
"args": [],
"options": {
"--": [],
"aaa-bbb": true,
"ccc": false
}
}
The "aaa-bbb": true
is always there.
const cli = require('cac')()
cli.use(updateNotifier)
cli.use(autoComplete)
cli.use(ensureOptions({
command: 'init',
options: ['name']
}))
vuepress dev --help # works and show help
vuepress dev -h # run dev's handler
When we have field choices
of option
, we have to give flag one precise value.
const cac = require('../')
const cli = cac()
cli.command('a', {
desc: 'command a',
}).option('foo', {
desc: 'foo is a flag for command a'.
type: "string",
default: "aa",
choices: ["aa", "bb", "cc"]
})
We have to give the value to flag of foo
. like that:
$ cli a
# error msg
The value of flag "foo" should be one of: "aa","bb","cc"
$ cli a --foo aa
# pass
Lines 13 to 14 in 5060bd9
This should be removed in esm bundle for Node.js too.
Normally it won't cause issues, unless you're trying to bundle your CLI into a single js file.
Using a rollup plugin to remove it, could be added here: https://github.com/cacjs/cac/blob/master/rollup.config.js
The most bare bone example I could come up with
$ node index someUnknownCommand
// index.js
require('cac')().parse()
/home/superuser/git/cac-test/node_modules/cac/dist/cac.js:171
const commandText = chalk.magenta(this.opts.displayCommands ? '<command> ' : `${this.command.command.name} `);
^
TypeError: Cannot read property 'command' of undefined
at Help.getHelp (/home/superuser/git/cac-test/node_modules/cac/dist/cac.js:171:98)
at Help.output (/home/superuser/git/cac-test/node_modules/cac/dist/cac.js:207:31)
at Cac.showHelp (/home/superuser/git/cac-test/node_modules/cac/dist/cac.js:378:10)
at Object.<anonymous> (/home/superuser/git/cac-test/index.js:3:5)
at Module._compile (module.js:662:30)
at Object.Module._extensions..js (module.js:673:10)
at Module.load (module.js:575:32)
at tryModuleLoad (module.js:515:12)
at Function.Module._load (module.js:507:3)
at Function.Module.runMain (module.js:703:10)
The only way I could get rid of this error was by adding a matching command such as *
or someUnknownCommand
.
Tested in version 4.4.2
, 4.4.1
, 4.4.0
and 4.3.7
.
There doesn't seem to be mention of this, so not sure if possible.
Example:
my-command remote rm origin
Maybe this is actually a feature request. I can change it if so.
$ node cli.js --help
...blah
For furthur helps on a specific command:
node cli.js <command> --help
Works CAC with Deno.
Deno throws error.
error: Uncaught ImportPrefixMissing: relative import path "events" not prefixed with / or ./ or ../ Imported from "https://unpkg.com/[email protected]/mod.js"
► $deno$/dispatch_json.ts:40:11
at DenoError ($deno$/errors.ts:20:5)
at unwrapResponse ($deno$/dispatch_json.ts:40:11)
at sendAsync ($deno$/dispatch_json.ts:91:10)
Deno does not have 'events' module but cac depends.
Also Deno cannnot import https://www.npmjs.com/package/events with such code.
Line 1 in cb367c9
import { cac } from 'https://unpkg.com/cac/mod.js'
cli.command('run', {
desc: 'run a file',
addtionalMatch(firstArg) {
return /.+\..+/.test(firstArg)
}
}, (input, flags) => {
console.log('run file:', input[0])
})
Then followings will do the same thing:
./cli.js run index.js
./cli.js index.js
But not this one:
./cli.js foo
this error occurs in cli, you can check here https://github.com/vitejs/vite/pull/3182/checks?check_run_id=3092027510
this is during the server test running with jest
, but idk how cac
take charge of it
> node cli.js --fo
> invalid cli flag `--fo`, did you mean `--foo`?
I'd like to be able to just specify a type (eg. String
), without making it an array String (eg. [String]
)
I'd like to be able to do:
const cli = cac('cli')
.option('--size <size>', 'Size', {
type: String,
})
and get the parsed options:
{ '--': [], size: '16' }
Instead, I currently must do
const cli = cac('cli')
.option('--size <size>', 'Size', {
type: [String],
})
and get the parsed options:
{ '--': [], size: ['16'] }
as communicated by the type:
Line 5 in f51fc22
run code below:
cli.command('[...rest]', 'desc')
.option('--out', 'desc')
.action(...)
const parsed = cli.parse(['', '', './src', '--out', './lib'], { run: false});
console.log(parsed)
{
"args": [
"./src",
"abc"
],
"options": {
"--": [],
"out": "./lib"
}
}
{
"args": [
"./src"
],
"options": {
"--": [],
"out": true
}
}
Currently you have to manually type cac
in Deno using // deno-types="https://unpkg.com/cac/mod.d.ts"
. I think we can actually get rid of all the Node.js dependencies and publish TypeScript source code so you can directly use it in Deno.
The coerce option config to run/coerce the results to the returned options.
The coerce option doesn't seem to run.
It appears the coerce option is never run in cac source. cac should run the coerce option on options with a coerce option defined.
If you have a wildcard command like:
cli.option('foo', desc)
const wildcard = cli.command('*', desc, handler)
wildcard.option('bar', desc)
And when you run cli.js --help
it currently prints message that includes bar
instead of foo
. Which means you can never see root help!
We should actually make wildcard command show root help instead of command help, and force user to add an alias name to the wildcard command so that it's possible to get the help for your wildcard command too:
cli.option('foo', desc)
const wildcard = cli.command('*', { alias: 'hi', desc }, handler)
wildcard.option('bar', desc)
In this case, run cli.js --help
for root help, run cli.js hi --help
for the help of your wildcard command.
I am looking to implement cac into DocPad. However, I need to be able to specify options before or after commands.
Here is an example source file:
'use strict'
const cli = require('cac')()
cli.option('opt', {
type: 'boolean',
desc: 'a boolean option'
})
cli.command('cmd', {
desc: 'a command'
}, function (input, flags) {
console.log('cmd:', { input, flags })
})
cli.command('*', {
desc: 'show help'
}, function (input, flags) {
cli.showHelp()
})
cli.parse()
And here is its output:
> node cac.js --opt cmd
cac.js 6.81.0
project description
USAGE
cac.js <command> [options]
COMMANDS
cmd a command
* show help
GLOBAL OPTIONS
-v, --version Display version [Type: boolean]
-h, --help Display help (You're already here) [Type: boolean]
--opt a boolean option [Type: boolean]
> node cac.js cmd --opt
cmd: { input: [],
flags:
{ version: false,
v: false,
help: false,
h: false,
opt: true,
'--': [] } }
The former output should behave the same as the latter output.
To have access to the CAC
class so can extend it easier.
Don't have.
Otherwise we are going oldschool
function Foo() {
this.bar = 123;
}
Foo.prototype = Object.getPrototypeOf(cac());
const qux = new Foo()
console.log(qux.bar)
console.log(qux.command)
console.log(qux.parse)
$ command subcommand --help
Subcommand is used to... <---- this would be nice to be able to add
Usage:
$ command subcommand
Options:
-v, --version Display version number
-h, --help Display this message
$ command subcommand --help
Usage:
$ command subcommand
Options:
-v, --version Display version number
-h, --help Display this message
Add per command options to customize output to user.
cli.command('subcommand', 'description here')
.help(`
${this.description}
${chalk.yellow(...)}
...
`)
// global examples
cli.example(`{bin} init project-name --force`)
// command examples
const command = cli.command('build')
command.example('{bin} build src/index.js')
cli.option('--externals <external>', 'Add externals (can be used for multiple times)', {
type: [String]
})
--externals foo
# options: { externals: ['foo'] }
--externals foo --externals bar
# options: { externals: ['foo', 'bar'] }
--externals foo
# options: { externals: 'foo' }
--externals foo --externals bar
# options: { externals: ['foo', 'bar'] }
Re: #24
Add a type
option to cli.option
and command.option
methods.
Providing both an --option and a --no-option variant causes the option to always be boolean.
This means even if --option takes in a [value] or , since --no-option is defined, it will only ever be true or false, and specifying a value causes it to be interpreted as an argument instead.
Example repo: https://github.com/spiltcoffee/cac-no-example
i would like to be able to only conditionally display the help which is currently not possible.
Lines 31 to 39 in 03822fc
--no-clean Don't clean output directory (default: true)
This is misleading, you might think --no-clean
is enabled by default.
If multi-character option alias ends with a character that is used as a single-character alias for another option args will be parsed incorrectly and the value will be assigned to that single-character option
Example:
const cac = require('cac');
const cli = cac({ bin: 'package-name' });
cli
.command('*', { desc: 'Example' }, (input, opts) => {
console.log(opts);
})
.option('option', {
alias: 'o', type: 'string',
default: 'option-default',
})
.option('anotheroption', {
alias: 'ao', type: 'string',
default: 'another-option-default',
});
cli.parse();
If I then execute it I get:
$ node ./cli.js -ao provided-ao-value
{ version: false,
v: false,
help: false,
h: false,
a: true,
o: 'provided-ao-value',
option: 'provided-ao-value',
anotheroption: 'another-option-default',
ao: 'another-option-default',
'--': [] }
expected
{ ...
o: 'option-default',
option: 'option-default',
anotheroption: 'provided-ao-value',
ao: 'provided-ao-value',
'--': [] }
I would have assumed that multi-character aliases aren't supported, but poi
has some, so it looks like it should be.
Cac version 4.4.4
cli
.command('format [...files]', 'Format files using Prettier')
.example("format 'src/**/*.{js,ts,json}'")
.alias(['fmt', 'fromat', 'foramt'])
// or separate arguments
.alias('fmt', 'fromat', 'foramt')
.action(async (files) => {})
None.
There are no examples on how to supply an array. Have tried a bunch of different ways.
command --array ["/","/about"]
Deno recently removed the usage of window.location.pathname
which now break the fix that was implemented in #70
cac
working with Deno
Rising an exception
Replace deno && typeof window !== 'undefined' && window.location.pathname
with import.meta.url
Sometimes I want the help message to be displayed after some asynchronous actions.
cli
.help(async () => await action())
Export the CAC class please, we need it while writing with typescript.
Because it increases the install size for Node.js users (around 18kb)
I have spent the best of the last three hours trying innovative ways to make my VSCode understand the typing definitions in this TypeScript program from my client deno
cli app. Alas, I have failed.
My attempt consisted of trying to leverage deno compiler options to add types like this:
// @deno-types="https://raw.githubusercontent.com/firstdoit/cac/master/mod.d.ts"
export { cac } from 'https://raw.githubusercontent.com/firstdoit/cac/master/mod.js'
And then to generate said mod.d.ts from the types/*
directory.
It's apparently impossible, because you inline EventEmitter (#66) but all "dts generation tools" I tried were unable to inline the events
library.
Tools I tried to create a single dts:
https://github.com/Swatinem/rollup-plugin-dts
https://github.com/timocov/dts-bundle-generator
You can take a look at my commits if it helps:
drawveloper@44e763c
drawveloper@4637d6b
I'm giving up on cac
for now, since apparently deno
is really at a stage when it's best to only import .ts
, deno-native libs directly. :(
cli
.command('create [dir]')
.option('--template <template>')
If I didn't pass the template
, it should throw an error like:option "template" value is missing
.
No error, continue to execute
Command.ts
- checkOptionValue
A simple cac
cli written in node with one command should find the command when called.
e.g.
Create a file cli.ts
with contents:
const cli = cac('act')
cli
.command('install [...apps]', 'Install apps')
.action((apps: any, options: any) => {
console.log('Wow! Deno cli!', apps, options)
})
cli.parse()
Then run deno install cli cli.ts
and finally cli install a
Expected behavior: the console.log
should execute
Nothing happens! Because the library cannot find the command given incorrect argv
handling.
If you call it with an extra argument, though it works:
cli whatever install a
Result: console.log
is called.
I believe this happened because of this change: denoland/deno#2123 (comment)
Now, Deno.args
only has the actual arguments.
This is the source of the problem:
Line 6 in aa8f868
In node
, the argv
process has two "useless" elements in the beginning: the node
executable itself, and the path to the script which started execution:
https://nodejs.org/docs/latest/api/process.html#process_process_argv
And finally, here is where we "remove" those two useless args, but they're not useless for Deno! They are the args.
Line 178 in aa8f868
There are some possible solutions. Simplest would be to add the "script path" to the processArgs variable, to be "backwards compatible" with node code. Do you want me to PR that, or do you see a better alternative?
Thanks for the great lib!
I passed a number out of precision:
cli -n 1002822319600013312
got n = 1002822319600013300, a number
cli -n "1002822319600013312"
also got n = 1002822319600013300, a number
May be better to parse parameter as string first and auto convert to number if within precision. :)
I see this project currently uses mri
(https://www.npmjs.com/package/mri) to parse the arguments.
I suggest we discuss using the standard flags
(https://deno.land/std/flags/) module when running on Deno so it behaves consistently with other cli's created in deno.
const requiredOptions = require('cac-required-options')
cli.option('foo', {
required: true,
desc: 'this is foo'
})
const someCommand = cli.command('some', 'this is some command')
someCommand.option('bar', {
required: true,
desc: 'this is bar'
})
cli.use(requiredOptions())
this.firstArg = this.firstArgs.startsWith('-') ? null : this.firstArgs;
^
TypeError: Cannot read property 'startsWith' of undefined
Looks like firstArgs never defined.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.