Reduce Friction with Automated Package Generation

April 05, 2020

When you’re working on a component library for your organization, you want your teammates to contribute. If your teammates aren’t contributing to your efforts, it could mean a couple of different things.

  1. They don’t believe in the initiative or don’t understand the value of component libraries
  2. It’s too difficult to get started
  3. The organization is prioritizing short-term wins over long-term wins, and PMS / engineering managers are not supporting developers’ efforts to contribute

As an individual contributor, you might not be able to solve problems #1 and #3, but you can make it easier to contribute to your library by providing developers with tools to get up and running more quickly. By simplifying the process, you’re also positively impacting problems #1 and #3 indirectly.

An example is package creation. Every single person who adds a component to your library needs to create a new package.

When using TypeScript, a contributor needs to set up a package.json, tsconfig.json, and a README.md that matches the conventions you have in your project. This can be time-consuming, depending on how familiar the contributor is with the library. It’s also something that you can automate in an afternoon. It’s a high-value, low-effort win.

You should start by creating an example package that can serve as a guide for people new to the repo. It’ll probably have a package.json with some commonly used scripts, and a tsconfig.json file with common defaults set.

You can take a look at what an example package might look like here.

Once you have an example package, you can write a script to allow developers to generate a new package from the command line.

I wrote a script like this that almost every contributor to my organization’s component library has used. You can use this script as an example to write one that works with your library’s filesystem.

const fs = require('fs');
const path = require('path');
const inquirer = require('inquirer');
const { exec } = require('child_process');
// Pull the arguments off of the command
let args = process.argv.slice(2);
// Write your own custom questions that the script will ask
const cliQuestions = [
{
type: 'input',
name: 'description',
message: 'What should the package.json description be?',
},
{
type: 'input',
name: 'version',
message: 'What should the package.json version be?',
},
];
// Find the path in the `/packages` directory where the package
// is going to live
const getPathToFile = packagesDir => {
return fs
.readdirSync(packagesDir)
.map(filename => path.join(packagesDir, filename))
.filter(filePath => filePath.includes('package.json'))[0];
};
// Update the example package's package.json file with the
// user's responses to the cliQuestions
const editPackageJson = packageName => {
inquirer.prompt(cliQuestions).then(answers => {
const packagesDir = path.resolve(`./packages/${packageName}`);
const content = {
name: `@maecapozzi/${packageName}`,
description: answers.description,
version: answers.version,
};
const pathToFile = getPathToFile(packagesDir);
const file = require(pathToFile);
const editedFile = JSON.stringify(
Object.assign(file, {
...content,
})
);
return fs.writeFileSync(pathToFile, editedFile, err => {
if (err) {
console.error(err);
return;
}
console.log('You successfully update the package.json file.');
});
});
};
const execute = packageName => {
if (packageName[0] === undefined) {
throw Error('Please provide a package name');
}
if (packageName.length > 1) {
throw Error('Please only provide a single package name');
}
if (fs.existsSync(`./packages/${packageName[0]}`)) {
throw Error('Please provide a package name that does not already exist');
}
// copy the example package to the new path
exec(
`cp -r ./examples/example-package ./packages/${packageName}`,
(err, stdout, stderr) => {
if (err) {
throw err;
}
console.log(stdout);
console.log(stderr);
console.log(`${packageName} has been successfully created`);
editPackageJson(packageName);
}
);
};
execute(args);
view raw generatePackage.js hosted with ❤ by GitHub

When the script is run, the output looks something like this:

$ yarn generate-package new-package

  yarn run v1.19.1

$ babel-node scripts/generatePackage.js new-package
  new-package has been successfully created
  ? What should the package.json description be? New package description
  ? What should the package.json version be? 1.0.0
✨  Done in 13.32s.

Let's talk coding.

I send articles about working as a software engineer, tips and tricks about React and building component libraries, and the occasional personal post. You can unsubscribe at any time.

All content © Mae Capozzi