I have been working on a series of CLI and dev tools over the past several months, and I have been trying to figure out the easiest way to come up with default configurations for said tools. One of the ways I have settled on is to assume that the majority of my users will have Git installed on their machine. As such, we should be able to pull name and email address from that configuration at the very least.
With a function like the following, we can retrieve Git configurations for use in our scrips.
1 2 3 4 5 6 7 8 9 10 11 |
const { exec } = require('child_process'); async function retrieveGitDefaults() { return Promise.all( ['name', 'email'].map(key => new Promise((resolve, reject) => { exec(`git config user.${key}`, (err, stdout, stderr) => { resolve(!!err ? null : String(stdout).trim()); }); })) ); } |
How would we use something like this? Well, in my case, I want to prompt the user for some configuration information, but I’d like to default the values to those of their local Git config, since this likely best reflects their preference. I like to use Inquirer.js for this type of thing, and the following snippet shows our function above in use for creating such a prompt.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
const inquirer = require('inquirer'); prompt(); async function prompt() { // retrieve our defaults const [name, email] = await retrieveGitDefaults(); const questions = [{ name: 'name', message: 'Your name:', default: name }, { name: 'email', message: 'Your email:', default: email }]; inquirer.prompt(questions).then(answers => { console.log('ANSWERS:', answers); }); } |
If you’re going to use a technique like this, one of the keys to remember is that everyone will configure their own environment, so no matter what you think is the gold standard, you always need to fail gracefully. In our case, let’s look at the breakdown of the first function.
First, we make sure we declare the function as async
for easy use:
1 |
async function retrieveGitDefaults() { |
Next, we want to be able to extend this to other attributes as needed, so we use the parallel promise resolver:
1 |
return Promise.all( |
Since this takes an array of promises, we will use Array.map to build promises for each of the values we wish to retrieve:
1 2 3 |
['name', 'email'].map(key => new Promise((resolve, reject) => { // ... })); |
Each of our promises executes a git command, so let’s use interpolation to make them easy to read:
1 2 3 |
exec(`git config user.${key}`, (err, stdout, stderr) => { // ... }); |
Finally, we want to be sure we always return something for our values, even on failure. In this case, a null
value is desired when we cannot retrieve from Git. Additionally, we do just a little cleanup of the string, since STDOUT will regularly have trailing newline characters.
1 |
resolve(!!err ? null : String(stdout).trim()); |
At this point, we can be confident we have either retrieved the Git config for both user.name
and user.email
or a null value for the one(s) that failed. Since we know we are retrieving exactly two values, we can create named variables using destructuring assignment:
1 |
const [name, email] = await retrieveGitDefaults(); |
This type of leveraging existing system level configuration can save your users immense amounts of time when they are using your tools, so I would strongly encourage you to find the patterns that work best for you.
I hope this is of some help, and as always, feel free to send me any questions or suggestions for improvement!