/mkCli

Simple development environment cli generator for nix

Primary LanguageNix

mkCli

This is a simple nix library to help generate development environment CLIs for projects.

Motivation

I often find myself wanting to create development environment scripts for common things like testing, linting, auto-fixing code, etc. Further, these tasks are composed of sub-tasks that are determined by the languages I’m working with, tooling nuances for the project, etc. However, in general, I pretty much always want the ability to group those commands in logical ways and parallelize the runs of those commands (in addition to running those commands manually) – for instance, I want to be able to run some singular test command and spawn off processes for each language-specific testing utility.

This library provides a simple nix utility for generating a such a cli script.

Example

Consider the following flake.nix:

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs";
    flake-utils.url = "github:numtide/flake-utils";
    mkCli.url = "github:cprussin/mkCli";
  };

  outputs = {
    nixpkgs,
    flake-utils,
    mkCli,
    ...
  }: (
    flake-utils.lib.eachDefaultSystem
    (
      system: let
        pkgs = import nixpkgs {
          inherit system;
          overlays = [mkCli.overlays.default];
          config = {};
        };

        cli = pkgs.lib.mkCli "cli" {
          _noAll = true;

          test = {
            lint = {
              nix = "${pkgs.statix}/bin/statix check .";
              js = "${pkgs.nodejs}/bin/npm exec eslint **/**";
            };
            dead-code.nix = "${pkgs.deadnix}/bin/deadnix .";
            format = {
              nix = "${pkgs.alejandra}/bin/alejandra --check .";
              js = "${pkgs.nodejs}/bin/npm exec prettier --check .";
            };
          };

          fix = {
            lint = {
              nix = "${pkgs.statix}/bin/statix fix .";
              js = "${pkgs.nodejs}/bin/npm exec eslint --fix";
            };
            dead-code.nix = "${pkgs.deadnix}/bin/deadnix -e .";
            format = {
              nix = "${pkgs.alejandra}/bin/alejandra .";
              js = "${pkgs.nodejs}/bin/npm exec prettier --write .";
            };
          };
        };
      in {
        devShells.default = pkgs.mkShell {
          buildInputs = [
            pkgs.git
            pkgs.nodejs
            cli
          ];
        };
      }
    )
  );
}

Then, inside your nix shell you’ll be able to run the following:

> cli -h              # Get a help string for the entire CLI
> cli test -h         # Get a help string for just the test sub-tree of the CLI
> cli test            # Run all test commands in parallel (with a prefix prepended to output)
> cli test lint nix   # Only run the `statix check .` command

Installation

Use the default overlay in the flake, which will inject mkCli onto pkgs.lib:

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs";
    flake-utils.url = "github:numtide/flake-utils";
    mkCli.url = "github:cprussin/mkCli";
  };

  outputs = {
    nixpkgs,
    flake-utils,
    mkCli,
    ...
  }: (
    flake-utils.lib.eachDefaultSystem
    (
      system: let
        pkgs = import nixpkgs {
          inherit system;
          overlays = [mkCli.overlays.default];
          config = {};
        };

        cli = pkgs.lib.mkCli "cli" { ... };
      in {
        devShells.default = pkgs.mkShell {
          buildInputs = [ cli ];
        };
      }
    )
  );
}

Usage

Pretty simple, just pass mkCli two arguments: the first is a string which specifies the name of the CLI script, and the second is an attrset of either attrsets or strings. The second argument should be a tree that represents what the CLI should do. String values indicate leaf nodes, attrset values indicate sub-commands. If you do not specify _noAll = true; on a particular node, then an all command will be generated which will run all descendant leaf nodes in parallel (this will also become the default command if no argument is passed, instead of showing help for that node).