Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mk

mk is a small shortcut runner for project-local commands.

Define commands in mk.json, mk.yaml, or mk.yml, then run them globally from inside that project:

mk
mk test ./...
mk --help
mk edit

Install

go install github.com/AaronKaa/mk@latest

For local development:

go install .

Tagged releases include prebuilt binaries for:

  • Linux amd64
  • Linux arm64
  • Linux armv6 for Raspberry Pi Zero / Pi 1
  • Linux armv7 for Raspberry Pi 2/3/4/5 on 32-bit OS
  • macOS amd64 and arm64
  • Windows amd64 and arm64
  • FreeBSD amd64 and arm64

Config

{
  "header": "Example Project Commands",
  "commands": {
    "test": {
      "command": "go test",
      "open": true,
      "help": "Run tests.",
      "usage": "mk test [packages...]",
      "group": "Quality"
    },
    "build": {
      "command": "go build ./...",
      "help": "Build the project.",
      "usage": "mk build",
      "group": "Quality"
    },
    "check": {
      "commands": [
        "go test ./...",
        "go vet ./..."
      ],
      "help": "Run all checks.",
      "usage": "mk check",
      "group": "Quality"
    }
  }
}

Commands default to open: false, so most commands do not need to include the field. When open is true, any extra arguments passed to mk are shell-quoted and appended to the configured command. For example:

mk test ./internal/...

runs:

go test ./internal/...

If a command needs arguments in the middle of the command, use placeholders:

{
  "command": "echo first={{arg1}} all={{args}}",
  "open": true
}

Supported placeholders are {{args}}, {{arg1}}, {{arg2}}, and {{arg3}}. When a placeholder is present, arguments are inserted at the placeholder instead of appended.

Use {{args_prefix "--flag"}} when a flag should only appear if arguments were provided:

commands:
  test:
    commands:
      - go test ./... {{args_prefix "-run"}}
      - go test ./internal/... {{args_prefix "-run"}}
    usage: mk test [filter]

mk test omits -run. mk test UserTest expands to -run UserTest. Use {{arg1_prefix "--flag"}} if only the first argument should be used.

Placeholders also work without open: true. In that case, arguments are only inserted where placeholders appear and are not appended to the end:

{
  "command": "cp {{arg1}} dist/{{arg1}}",
  "help": "Copy one file into dist.",
  "usage": "mk copy <file>"
}

Running mk copy app.js executes:

cp app.js dist/app.js

Use commands instead of command for a shortcut that should run multiple commands in order. For multi-command blocks with open: true, extra arguments are appended to each command only when arguments are provided:

{
  "commands": [
    "go test",
    "go vet"
  ],
  "open": true
}

More Features

Run dependencies before a command:

commands:
  build:
    deps: [generate, lint]
    command: go build ./...

Disable a command without deleting it:

commands:
  old-build:
    command: go build ./cmd/old
    disabled: true

Disabled commands are not shown in the normal command list, help output, or shell completions, and they cannot be run until re-enabled. They still appear greyed out in mk edit.

Set global or per-command environment variables:

env:
  APP_ENV: development
env_file:
  - .env
commands:
  test:
    command: go test ./...
    env_file: .env.test
    env:
      CGO_ENABLED: "0"

env_file accepts either a string or a list of files. Relative paths are resolved from the directory containing mk.json or mk.yaml. Files use dotenv-style KEY=value lines, with optional export KEY=value syntax. Precedence is: global env_file, global env, command env_file, command env.

Define reusable variables and expand them with {{NAME}}:

vars:
  PROGRAM: foo
  CC: cc
  CFLAGS: -Wall -pedantic
commands:
  build:
    command: "{{CC}} {{CFLAGS}} -o {{PROGRAM}}"

Variables can also come from shell commands. They are evaluated from the command working directory before the command runs, and are also exposed as environment variables to that command:

vars:
  C_FILES:
    shell: "printf '%s' *.c"
commands:
  list-c:
    command: "echo {{C_FILES}}"

Command-level variables override global variables:

vars:
  TARGET: ./...
commands:
  test-one:
    vars:
      TARGET: ./internal/config
    command: go test {{TARGET}}

Run from a specific directory:

commands:
  docs:
    dir: website
    command: npm run build

Force every command to run from a specific path, or override one command to ignore the global path_force with @:

path_force: tools
commands:
  build-tools:
    command: go build ./...
  local:
    path_force: "@"
    command: pwd

@ only disables the inherited global path_force for that command. After that, normal resolution still applies, so the command runs from the config directory unless it also sets dir. In YAML, @ must be quoted as "@".

Customize the help/list title:

header: My Project Commands

Group commands in the command list:

commands:
  test:
    group: Quality
    command: go test ./...

Create aliases:

commands:
  test:
    command: go test ./...
    aliases: [t]

Run multi-command blocks in parallel:

commands:
  checks:
    commands:
      - go test ./...
      - go vet ./...
    parallel: true

Require confirmation before running a command:

commands:
  deploy:
    command: ./scripts/deploy
    confirm: true

mk searches the current directory and then parent directories for mk.json, mk.yaml, or mk.yml. Commands run from the directory containing the config file.

Environment Config

You can provide a full command set through the MK_COMMANDS environment variable. The value can be JSON or YAML using the same schema as mk.json/mk.yaml.

export MK_COMMANDS='{"commands":{"ci-test":{"command":"go test ./...","help":"Run CI tests."}}}'
mk ci-test

To generate a shell-safe MK_COMMANDS export from the local config:

mk env
mk env --yaml

If no mk.json, mk.yaml, or mk.yml is found, mk uses MK_COMMANDS directly.

If a config file is found, mk merges MK_COMMANDS into the file-backed project by default. Local file commands and aliases win; conflicting environment commands are skipped and only non-conflicting commands are inherited.

To keep environment commands isolated, set MK_COMMANDS_PREFIX; every environment command, dependency, and alias is prefixed to avoid conflicts:

export MK_COMMANDS='{"commands":{"test":{"command":"npm test","aliases":["t"]}}}'
export MK_COMMANDS_PREFIX='ci:'

mk ci:test
mk ci:t

When environment commands are merged into a file-backed project, their global path_force, vars, env, and env_file are applied to the inherited commands themselves and do not overwrite the local file's global config.

Environment-provided commands are shown in a separate inherited section in mk and mk --help.

If you want environment commands to remain runnable but stay out of the command list when a local file is present, use hide globally or per command:

hide: true
commands:
  ci-test:
    command: npm test
commands:
  ci-test:
    command: npm test
    hide: true

hide only affects environment commands merged into a file-backed project. Hidden commands still run if you call them directly.

Commands

mk
mk init
mk init --json
mk edit
mk --convert yaml
mk --convert json
mk --debug-config
mk --dry-run test ./...
mk completion zsh
mk env
mk convert-make Makefile
mk --help
mk help test
mk test [args...]

Running mk by itself shows the browsable project command list. Running mk --help shows usage plus the same command list.

mk init creates a minimal YAML starter config with a single ls command so you can see the format without having to clean up a larger template. Use mk init --json if you want JSON instead.

mk --debug-config prints the resolved config source state, including local commands, inherited environment commands, hidden inherited commands, and skipped environment commands with the reason they were skipped.

mk --convert yaml converts mk.json to mk.yaml and removes the original JSON file. mk --convert json converts mk.yaml or mk.yml to mk.json and removes the original YAML file. Conversion refuses to overwrite an existing target file.

mk convert-make [Makefile] [--json|--yaml] [-o path] converts common Makefiles into mk config. It maps section comments to groups, comments/help output to descriptions, simple Make assignments to vars, $(NAME) references to {{NAME}}, explicit target dependencies to deps, cd dir && ... to dir, strips @, and skips the reserved help target.

The converter is intentionally best-effort. It warns when it sees Make-only features that need manual review, such as pattern rules, automatic variables like $@ and $<, include, conditionals, target-specific variables, and Make functions like $(wildcard ...) or $(patsubst ...).

The editor is built with Charmbracelet Bubble Tea and writes back to the existing config format. It opens as a browsable command list:

g globals  enter/e edit  n new  d delete  x enable/disable  ctrl+s/s save  up/down select  q quit

Inside the edit form:

tab next field  shift+tab previous  space toggle bool  ctrl+s save  esc command list

The global editor covers header, path_force, env_file, env, vars, and hide.

The command editor shows name, command, help, and an advanced switch first. Advanced fields cover aliases, usage, group, dir, path_force, deps, env_file, env, vars, open, parallel, confirm, hide, and disabled.

Editor field formats:

  • Lists are comma-separated, for example .env, .env.local
  • env uses KEY=value, OTHER=value
  • vars uses KEY=value for plain values and KEY:=command for shell-backed values

About

A small shortcut runner for project-local commands.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages