Home

Awesome

cmdx

Build Status Go Report Card GitHub last commit License

Task runner. It provides useful help messages and supports interactive prompts.

Overview

cmdx is the task runner. Using cmdx you can manage tasks for your project such as test, build, format, lint, and release.

For example, This is the tasks for cmdx itself.

$ cmdx -l
init, i - setup git hooks
coverage, c - test a package (fzf is required)
test, t - test
fmt - format the go code
vet, v - go vet
lint, l - lint the go code
release, r - release the new version
durl - check dead links (durl is required)
ci-local - run the Drone pipeline at localhost (drone-cli is required)

$ cmdx help release
NAME:
   cmdx release - release the new version

USAGE:
   cmdx release <version>

DESCRIPTION:
   release the new version
ARGUMENTS:
   version

cmdx searches the configuration file from the current directory to the root directory recursively, and runs the task at the directory where the configuration file exists.

Features

Install

cmdx is a single binary written in Go. So you only need to put the executable binary into $PATH.

  1. Homebrew
brew install suzuki-shunsuke/cmdx/cmdx
  1. aqua
aqua g -i suzuki-shunsuke/cmdx
  1. Download a prebuilt binary from GitHub Releases and install it into $PATH

  2. Go

go install github.com/suzuki-shunsuke/cmdx/cmd/cmdx@latest

Getting Started

Create the configuration file.

$ cmdx --init
$ cat .cmdx.yaml

Edit the configuration file and register the task hello.

$ vi .cmdx.yaml
$ cat .cmdx.yaml
---
tasks:
- name: hello
  description: hello command
  usage: hello command
  flags:
  - name: source
    short: s
    usage: source file path
    required: true
    input_envs:
    - NAME
  - name: switch
    type: bool
  args:
  - name: name
    usage: name
    default: bb
  environment:
    FOO: foo
  script: "echo hello {{.source}} $NAME {{if .switch}}on{{else}}off{{end}} {{.name}} $FOO" # use Go's text/template

Output the help.

$ cmdx help
NAME:
   cmdx - task runner

USAGE:
   cmdx [global options] command [command options] [arguments...]

VERSION:
   0.2.2

AUTHOR:
   Shunsuke Suzuki

COMMANDS:
   hello     hello command
   help, h   Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --config value, -c value  configuration file path
   --name value, -n value    configuration file name. The configuration file is searched from the current directory to the root directory recursively
   --init, -i                create the configuration file
   --list, -l                list tasks
   --help, -h                show help
   --version, -v             print the version

List tasks.

$ cmdx -l
hello - hello command

Run the task hello.

$ cmdx hello -s README
+ echo hello README $NAME off bb $FOO
hello README README off bb foo

$ cmdx hello -s README --switch
+ echo hello README $NAME on bb $FOO
hello README README on bb foo
$ cmdx hello --target foo
target: foo

Example

In order to understand cmdx, it is good to execute cmdx command actually. We prepare the sample configuration file for you to execute cmdx.

Please see examples.

Configuration

pathtypedescriptionrequireddefault
.timeouttimeoutthe task command timeoutfalse
.input_envs[]stringdefault environment variable bindingfalse[]
.script_envs[]stringdefault environment variable bindingfalse[]
.environmentmap[string]stringtop level environment variablesfalse{}
.quietboolDefault configuration whether the content of script is outputtedfalse
.tasks[]taskthe list of taskstrue
task.namestringthe task nametrue
task.shortstringthe task short namefalse
task.descriptionstringthe task descriptionfalse""
task.usagestringthe task usagefalse""
task.flags[]flagthe task flag argumentsfalse[]
task.args[]argthe task positional argumentsfalse[]
task.input_envs[]stringtask level environment variable bindingfalse[]
task.script_envs[]stringtask level environment variable bindingfalse[]
task.environmentmap[string]stringthe task's environment variablesfalse{}
task.scriptstringthe task command. This is run by sh -ctrue
task.quietbooltask level default configuration whether the content of script is outputtedfalse
task.shell[]stringshell command to run the script["sh", "-c"]
task.timeouttimeoutthe task command timeoutfalse
task.requirerequirerequirement of taskfalse{}
task.tasks[]tasksub tasksfalse[]
require.exec[]stringArrayrequired executable filesfalse[]
require.environment[]stringArrayrequired environment variablesfalse[]
stringArrayarray whose element is string or array of string
timeout.durationintthe task command timeout (second)false36000 (10 hours)
timeout.kill_afterintthe duration the kill signal is sent after timeout.durationfalse0, which means the command isn't killed
flag.namestringthe flag nametrue
flag.shortstringthe flag short namefalse
flag.usagestringthe flag usagefalse""
flag.defaultstringthe flag argument's default valuefalse""
flag.input_envs[]stringflag level environment variable bindingfalse[]
flag.script_envs[]stringflag level environment variable bindingfalse[]
flag.typestringthe flag type. Either "string" or "bool"false"string"
flag.requiredboolwhether the flag argument is requiredfalsefalse
flag.validate[]validateparameters to validate the value of flagfalse[]
flag.promptpromptpromptfalseprompt is disabled
prompt.typestringprompt typetrue
prompt.messagestringprompt messagefalseflag.name or arg.name
prompt.helpstringprompt helpfalse
prompt.options[]stringentries of select or multi_select prompttrue if the prompt type is select or multi_select
arg.namestringthe positional argument nametrue
arg.usagestringthe positional argument usagefalse""
arg.defaultstringthe positional argument's default valuefalse""
arg.input_envs[]stringthe positional argument level environment variable bindingfalse[]
arg.script_envs[]stringthe positional argument level environment variable bindingfalse[]
arg.requiredboolwhether the argument is requiredfalsefalse
arg.promptpromptpromptfalseprompt is disabled
arg.validate[]validateparameters to validate the value of argfalse[]
validate.typestringvalue type (email, url, int)false
validate.regexpstringthe regular expressionfalse
validate.min_lengthintthe minimum string lengthfalse
validate.max_lengthintthe maximum string lengthfalse
validate.prefixstringthe prefixfalse
validate.suffixstringthe suffixfalse
validate.containstringthe string which the value should containfalse
validate.enum[]stringenumfalse

script

task.script is the task command. This is parsed by Go's text/template package. sprig functions can be used. The value of the flag and positional argument can be referred by the argument name.

For example,

# refer the value of the argument "source"
script: "echo {{.source}}"

Multiple lines

script: |
  echo foo
  echo bar

If the positional argument is optional and the argument isn't passed and the default value isn't set, the value is an empty string "".

And some special variables are defined.

nametypedescription
_builtin.args[]stringthe list of positional arguments which aren't defined by the configuration args
_builtin.args_stringstringthe string which joins _builtin.args by the space " "
_builtin.all_args[]stringthe list of all positional arguments
_builtin.args_stringstringthe string which joins _builtin.all_args by the space " "

input_envs, script_envs

input_envs is a list of environment variables that are bound to the variable.

tasks:
- name: foo
  script: "echo {{.source}}"
  args:
  - name: source
    input_envs:
    - command_env
$ COMMAND_ENV=zzz cmdx foo
+ echo zzz
zzz

script_envs is a list of environment variables which the variable is bound to.

tasks:
- name: foo
  script: "echo $COMMAND_ENV"
  args:
  - name: source
    script_envs:
    - command_env
$ cmdx foo zzz
+ echo $COMMAND_ENV
zzz

timeout

cmdx supports the configuration about the timeout of the task.

  1. send SIGINT after timeout.duration seconds (default 36,000 seconds)
  2. if timeout.kill_after isn't 0, send SIGKILL after timeout.duration + timeout.kill_after seconds. By default timeout.kill_after is 0 so SIGKILL isn't sent

For example, the following task foo's timeout is 3 seconds.

tasks:
- name: foo
  script: sleep 100
  timeout:
    duration: 3
$ cmdx foo
+ sleep 100
the command is timeout: 3 sec

The task timeout configuration inherits the top level timeout configuration.

timeout:
  duration: 3
tasks:
- name: foo # the timeout.duration is 3
  script: sleep 100

require

task.require is the requirement to run the task.

require.exec

For example, in the following example both of curl and wget is required.

tasks:
- name: foo
  script: curl http://example.com
  require:
    exec:
    - curl
    - wget

If curl isn't installed, the task is failed.

$ cmdx foo
curl is required

Note that the shell's alias is ignored. Internally, exec.LookupPath is used.

In the following example, either curl or wget is required.

tasks:
- name: foo
  script: curl http://example.com
  require:
    exec:
    - - curl
      - wget
$ cmdx foo
one of the following is required: curl, wget

require.environment

require.environment is the required environment variables. Note that if the value of the environment variable is an emtpy string, the environment variable is treated as unset.

tasks:
- name: foo
  script: curl http://example.com
  require:
    environment:
    - GITHUB_TOKEN
$ cmdx foo
the environment variable 'GITHUB_TOKEN' is required
tasks:
- name: foo
  script: curl http://example.com
  require:
    environment:
    - - GITHUB_TOKEN
      - GITHUB_ACCESS_TOKEN
$ cmdx foo
one of the following environment variables is required: GITHUB_TOKEN, GITHUB_ACCESS_TOKEN

validation

cmdx supports to validate args and flags.

For example,

# .cmdx.yaml
tasks:
- name: hello
  script: echo hello
  args:
  - name: age
    validate:
    - type: int
$ cmdx hello foo
age is invalid: must be int: foo

quiet

By default cmdx outputs the content of task's script when the task is run.

In case of the following example, + echo hello is outputted.

# .cmdx.yaml
tasks:
- name: hello
  script: echo hello
$ cmdx hello
+ echo hello
hello

You can suppress the output by --quiet (-q) option.

# BAD: cmdx hello -q
$ cmdx -q hello
hello

And you can change the default configuration of quiet at the global level or the task level.

# .cmdx.yaml
tasks:
- name: hello
  script: echo hello
  quiet: true  # task level
# .cmdx.yaml
quiet: true # global level
tasks:
- name: hello
  script: echo hello
  quiet: true

The priority is

  1. command line flag
  2. task level configuration
  3. global level configuration

If the quiet is enabled by configuration but you want to output script, please set the flag -q=false.

# "=" is needed
$ cmdx -q=false hello
+ echo hello
hello

prompt

cmdx supports the interactive prompt by AlecAivazis/survey. cmdx supports the following prompt types.

About prompt type, please see AlecAivazis/survey's document.

value source priority

  1. command line arguments
  2. environment variable (input_envs)
  3. prompt (prompt isn't launched if the value is set by command line argument or environment variable)
  4. default value

shell

This is an advanced feature.

By default task's script is run by the command sh -c. You can change the command by the shell option.

For example, you can run Python script.

- name: hello
  shell:
  - python
  - -c
  script: |
    print("hello")

And you can run the shell script in the container.

- name: hello
  shell:
  - docker
  - exec
  - -ti
  - foo
  - sh
  - -c
  script: |
    whoami
    read -p "name?" name
    echo "name: $name"

Bash (Zsh) Completion

https://github.com/urfave/cli/blob/477292c8d462a3f51cd18bc77c0542193a62274d/docs/v2/manual.md#bash-completion

cmdx supports Bash (Zsh) Completion powered by urfave/cli.

We test the completion with Zsh, but we don't test the completion with other shell.

To enable the completion, you have to load a shell script. For detail, please see the document of urfave/cli.

Please set cmdx to PROG

Sub tasks

cmdx supports sub tasks.

For example,

tasks:
- name: admin
  usage: administrator feature
  tasks:
  - name: cluster
    usage: manage clusters
    tasks:
    - name: create
      usage: create a cluster
      script: echo "create a cluster"
$ cmdx admin cluster create
+ echo "create a cluster"
create a cluster

The following attributes are inherited from the parent tasks.

For example,

tasks:
- name: admin
  usage: administrator feature
  require:
    exec:
    - yamllint
  tasks:
  - name: cluster
    usage: manage clusters
    tasks:
    - name: create
      usage: create a cluster
      script: echo "create a cluster"
$ cmdx admin cluster create
yamllint is required

We can't set both task.script and task.tasks.

For example,

tasks:
- name: hello
  script: echo hello
  tasks:
  - name: world
    script: echo "hello world"
$ cmdx hello
please fix the configuration file: the task `hello` is invalid. when sub tasks are set, 'script' can't b
e set

Contributing

Please see the CONTRIBUTING.md.

License

MIT