DEV Community

Cover image for Enhance CLI with ZX
bsorrentino
bsorrentino

Posted on

Enhance CLI with ZX

The importance of CLI usage

As developer, usage of Command Line Interface (CLI) based tools are becoming more and more important in day by day work, especially now that devops practices is a first-citizen in development world.

In my day by day work I use a lot of CLI either for interactive use and for batch processing to accomplish various and different tasks

In this article i'd like to focus on os-independent CLI development using NodeJS and in particular "how to enhance a pre-existent CLI" using ZX an amazing package provided by Google.

Firstly a brief introduction to ZX project

The ZX project

ZX describes itself as A tool for writing better scripts and below there is its description pick upped from the site:

Bash is great, but when it comes to writing scripts, people usually choose a more convenient programming language. JavaScript is a perfect choice, but standard Node.js library requires additional hassle before using. The zx package provides useful wrappers around child_process, escapes arguments and gives sensible defaults.

The '$' command

Th $command executes a given string using the spawn function from the child_process package and returns

Built in functions

ZX comes with a list of built in functions useful to develop effective and/or interactive CLI . Below the complete list

function description
cd() Changes the current working directory.
fetch() A wrapper around the node-fetch package.
question() A wrapper around the readline package.
sleep() A wrapper around the setTimeout function.
nothrow() Changes behavior of $ to not throw an exception on non-zero exit codes.
quiet() Changes behavior of $ to disable verbose output.

Built in packages

ZX comes with a list of built in packages useful to develop effective CLI and/or interactive. Below the complete list

packages description
chalk Colorize output
yaml Parse/Serialize yaml format
fs-extra File System utility
globby User-friendly glob matching
minimist Argument options parser
witch require/resolve for binaries

As you can see ZX bring a lot of standard-de-facto packages and provides utilities focused on accomplish main CLI needs.
For this reason I chose it not only to develop new CLI but also to enrich the existent ones give them more interactivity and implementing common use-cases/workflows.

ZX - The Power Platform CLI (PAC) Use Cases

Lately I actively work with Power Platform a Microsoft's low-code platform that for managing its ALM has delivered a powerful CLI (PAC) and since I need very often to export/import Power Platform solutions to/from a git repository I've decided to enhance the PAC CLI using ZX developing a new CLI zx-powerapps-cli that implememts the complete workflows involving such tasks.
The goal here is not go in deep of Power Platform CLI but give you an idea of how I've used ZX to enhance and simplify its usage.
Below the description of main steps that involving import/export tasks that I've implemented using ZX.

Export Solution

  1. Authenticate against the platform environment
  2. Choose solution to export. (deciding if it should be exported as a Managed or Unmanaged Solution)
  3. Optionally publish all customization
  4. Export solution from platform environment
  5. Unpack Solution once exported to local git repository

Import Solution

  1. Authenticate against the platform environment
  2. Choose local solution folder to import
  3. Packing Solution. (deciding if it should be packed as a Managed or Unmanaged Solution)
  4. Import Packed Solution to platform environment

As you can see there are several steps and I wanted to ask the user to provide the requested information and confirmation for the optional ones.
So I have used the ZX $ to execute the commands behind-the-scene and question for ask info or confirmation, moreover just-in-case, I've also provided command line arguments to skip asking for.

Below some functions that I've develop to enrich PAC to give you an idea on how is simple use ZX to enhance (or implement) a CLI

Select Authentication Profile

In this example we use the PAC AUTH subcommand to list available authentication profiles and/or select one of them

export const askForAuthProfile = async () => {
    // Since 'minimist' is integrated a 'argv' is available out-of-box
    if( argv.authindex ) {
      // If a 'authindex' is provided as argument
      // select the given authentication profile
      await $`pac auth select --index ${argv.authindex}`     
      return
    }
    // list all available authentication profile  
    await $`pac auth list`
    // interactively ask for a profile
    const choice = await question('choose profile index (enter for confirm active one): ')
    // select the given authentication profile    
    await $`pac auth select --index ${choice}`
}
Enter fullscreen mode Exit fullscreen mode

Select Solution Folder

In this example we use the fs-extra and chalk built-in packages for asking a valid solution folder

export const askForSolutionFolder = async () => {
    let solution
    // Since 'minimist' is integrated a 'argv' is available out-of-box
    if( argv.solution ) {
      // If a 'solution' is provided as argument use it
      solution = argv.solution
    }
    else {
      // otherwise ask for it
      solution = await question('solution folder: ')
    }

    // Since 'fs-extra' and 'chalk' are integrated 'fs' and 'chalk' are available out-of-box
    try {
        // Folder validation
        const stats = await fs.stat( solution )
        if( stats.isDirectory() )
            return solution

        console.log( chalk.red(`solution folder '${solution}' is not a directory!`))            
    }
    catch( e ) {
        console.log( chalk.red(`solution folder '${solution}' doesn't exist!`))
    }
}

Enter fullscreen mode Exit fullscreen mode

Import a Solution in a Power Platform (Cloud) Environment

In this example we put all together providing a complete import workflow. We reuse the functions above and the PAC SOLUTION subcommand to packing and importing solution into remote environment related to the selected authentication profile

try {

    await askForAuthProfile()

    const solution = await askForSolutionFolder()

    // import Solution as 'Managed' into selected profile
    await $`pac solution pack --zipfile /tmp/${solution}_managed.zip -f ${solution} -p Managed -aw`  
    await $`pac solution import -p /tmp/${solution}_managed.zip -f -pc -a`        

} catch (p) {
    if (p.exitCode)
        console.log(`error occurred code: ${p.exitCode} error: ${p.stderr}`)
    else
        console.error(p)
}

Enter fullscreen mode Exit fullscreen mode

Conclusion

I consider ZX a good solution to enhance or implement multi-platform CLI based upon NodeJS.

If you are interested in implementation detail the code is on Github

I Hope that such article could help if you are dealing with CLI developing or enhancing. in the meanwhile happy coding and … enjoy ZX! πŸ‘‹

References


Originally published at https://bsorrentino.github.io on May 2, 2022.

Discussion (0)