CLI Concept
A Command Line Interface (CLI) is a tool or application that allows users to interact with a program via the command line to perform certain tasks. In frontend development, examples include vue-cli, vite, create-react-app, etc. These tools help reduce repetitive tasks, standardize workflows, and improve development efficiency.
Functional Goals
- Create a template to initialize a
Koaproject - Allow users to specify middleware and configuration via commands
- Specify the package manager
Implementation
To implement the above features, we generally need the following libraries:
inquirer– prompts users for configuration via interactive questionscommander– provides a CLI interface with command and argument supportejs– templating engine for creating project filesexeca– run child processes in a more user-friendly waychalk– add colors and styles to console output
Entry File
First, create an index.js file inside the bin directory:
import { Command } from "commander";
import { create } from "./command/create.js";
const program = new Command();
program.name("koa-inventor").description("A CLI tool for Koa").version("1.0.4");
program
.command("create")
.description("Create a Koa project")
.action(() => {
create();
});
program.parse();
By creating a Command instance, we can provide CLI metadata and prompts for users.
Next, we implement the create() function.
The create() function roughly performs:
- Create a project folder
- Create an entry file
- Create
package.json - Install project dependencies
Example implementation:
export async function create() {
const config = await createConfig();
const rootPath = `./${config.projectName}`;
config.rootPath = rootPath;
// 1. Create project folder
fs.mkdirSync(rootPath);
createEditorConfig(config);
createMiddleWareFile(config);
console.log(chalk.blue("Project folder created successfully"));
// 2. Create entry file
fs.writeFileSync(`./${rootPath}/index.js`, createBootstrapTemplate(config));
console.log(chalk.blue("index.js created successfully"));
// 3. Create package.json
fs.writeFileSync(
`./${rootPath}/package.json`,
createPackageJsonTemplate(config)
);
console.log(chalk.blue("package.json created successfully"));
// 4. Install dependencies
console.log(chalk.blue("Installing dependencies..."));
await installDependencies(config);
console.log(chalk.blue("Dependencies installed successfully"));
console.log(chalk.blue("Happy coding~~"));
}
Configuration
When running node index.js create, we need to create files based on user input. This is where inquirer comes in.
createConfig() function:
async function createConfig() {
return await inquirer.prompt([
projectNameConfig(),
middlewareConfig(),
portConfig(),
packageManagerConfig(),
]);
}
projectNameConfig() function:
function projectNameConfig() {
return {
type: "input",
name: "projectName",
validate(projectName) {
if (projectName) return true;
return "Please enter your project name!";
},
};
}
By running this, users can create projects interactively.
Templates & Files
We can use ejs to generate templates. For example, creating the entry file:
export function createBootstrapTemplate(config) {
const __dirname = fileURLToPath(import.meta.url);
const template = fs.readFileSync(
path.resolve(__dirname, "../../../templates/index.ejs")
);
const code = ejs.render(template.toString(), config);
return code;
}
Using
fileURLToPathto get__dirnameis necessary for ES modules in Node. Make surepackage.jsonincludes"type": "module".
Example index.ejs:
import Koa from 'koa'
<% if (middleware.includes('koa-static')) { %>
import serve from 'koa-static'
<% } %>
<% if (middleware.includes('koa-bodyparser')) { %>
import bodyParser from 'koa-bodyparser'
<% } %>
<% if (middleware.includes('koa-router')) { %>
import Router from 'koa-router'
import useRoutes from './router/index.js'
<% } %>
const app = new Koa()
<% if (middleware.includes('koa-static')) { %>
app.use(serve('./static'));
<% } %>
<% if (middleware.includes('koa-bodyparser')) { %>
app.use(bodyParser())
<% } %>
<% if (middleware.includes('koa-router')) { %>
app.useRoutes = useRoutes
app.useRoutes()
<% } %>
app.listen(<%= port %>, () => {
console.log('Server is running on <%= port %>')
})
You can similarly create an EJS template for package.json.
Installing Dependencies
After creating all configuration files, install dependencies according to package.json. Using execa:
import { execa } from "execa";
export async function installDependencies(config) {
const { packageManager, rootPath } = config;
const installCommand = packageManager.includes("npm")
? `${packageManager} install`
: "yarn";
await execa(installCommand, { cwd: rootPath });
}
Testing
In package.json, specify the bin field as the entry point, then run npm link to test the CLI locally.
Publishing
To share the CLI publicly:
npm login– log innpm publish– publish to NPM
Conclusion
This project was born from my own frustration. When developing with Koa, I repeatedly installed middleware and configured projects. This CLI allows rapid project creation, saving setup time.