Backend Development 17 min read

Developing a Node.js CLI Scaffold: Tools, Package.json and Demo

This guide walks you through building a custom Node.js CLI scaffold using commander, chalk, inquirer, ora, and download‑git‑repo, explains essential package.json fields like bin and engines, and provides a complete demo that prompts users, downloads templates, and initializes projects ready for global installation.

DaTaobao Tech
DaTaobao Tech
DaTaobao Tech
Developing a Node.js CLI Scaffold: Tools, Package.json and Demo

This article introduces the essential tools for building a CLI scaffolding project— commander , chalk , inquirer , ora and others—along with important package.json fields, and demonstrates a complete example.

Why a custom scaffold? While many projects start with npm init X , existing scaffolds may not meet specific business needs, so a custom CLI is required.

Core capabilities a scaffold should provide:

Interactive prompting

Template downloading

Template writing

Post‑processing (git init, dependency installation, etc.)

Tool overview

commander.js – command‑line parsing and sub‑command handling.

chalk – styling terminal output.

inquirer.js – interactive question prompts.

ora – spinner / loading indicator.

download-git-repo – fetch remote git templates.

commander example

import { Command } from 'commander';
const program = new Command();
program
  .name('test-string-utils')
  .description('CLI to some JavaScript string utilities by 禾汐')
  .version('0.8.0');
program.command('split')
  .description('Split a string into substrings and display as an array')
  .argument('
', 'string to split')
  .option('--first', 'display just the first substring')
  .option('-s, --separator
', 'separator character', ',')
  .action((str, options) => {
    const limit = options.first ? 1 : undefined;
    console.log(str.split(options.separator, limit));
  });
program.parse();

chalk usage

import chalk from 'chalk';
const log = console.log;
log(chalk.blue('Hello world!'));
log(chalk.red('Error'), chalk.underline.bgBlue('important'));
log(chalk.rgb(123,45,67).underline('Styled text'));
log(chalk.hex('#DEADED').bold('Bold gray!'));

inquirer example

import inquirer from 'inquirer';
const promptList = [{
  type: 'input',
  name: 'name',
  message: '设置一个用户名:',
  default: 'test_user'
}, {
  type: 'input',
  name: 'phone',
  message: '请输入手机号:',
  validate: val => val.match(/\d{11}/) ? true : '请输入11位数字'
}];
inquirer.prompt(promptList).then(answers => console.log(answers));

ora spinner

import ora from 'ora';
import chalk from 'chalk';
const spinner = ora(`Loading ${chalk.red('模板')}`).start();
setTimeout(() => spinner.succeed('模板下载成功!'), 3000);
setTimeout(() => spinner.fail('模板下载失败!'), 3000);

download‑git‑repo usage

import download from 'download-git-repo';
import rimraf from 'rimraf';
import path from 'path';
const dir = path.join(process.cwd(), 'template');
rimraf.sync(dir);
download('[email protected]:repo.git#master', dir, { clone: true }, err => {
  console.log(err ? 'Error' : 'Success');
});

package.json essentials

{
  "name": "ls",
  "version": "1.0.0",
  "type": "module",
  "scripts": { "test": "echo \"Error: no test specified\" && exit 1" },
  "dependencies": {
    "chalk": "^5.0.1",
    "commander": "^9.4.0",
    "download-git-repo": "^3.0.2",
    "inquirer": "^9.1.0",
    "ora": "^6.1.2",
    "rimraf": "^3.0.2"
  },
  "engines": { "node": ">=6.0.0", "npm": ">=3.0.0" },
  "bin": { "test-cli": "./bin.js" }
}

The bin field maps a command name to a script file, enabling global installation (e.g., npm install -g . ) and usage like test-cli -v .

Demo CLI implementation

#!/usr/bin/env node
import fs from 'fs';
import path from 'path';
import chalk from 'chalk';
import download from 'download-git-repo';
import ora from 'ora';
import inquirer from 'inquirer';
import { exec } from 'child_process';
import { Command } from 'commander';
import checkDir from './checkDir.js';

const commander = new Command();
const promptTypeList = [{
  type: 'list',
  name: 'type',
  message: '请选择拉取的模版类型:',
  choices: [
    { name: '天猫优品的业务脚手架', value: { url: '[email protected]:dinamic/miniapp-tracker.git#master', gitName: 'web', val: '天猫优品的业务脚手架' } },
    { name: '小程序', value: { url: '[email protected]:dinamic/miniapp-tracker.git#master', gitName: 'miniapp', val: 'miniapp' } }
  ]
}];

commander.version('1.1.1', '-v, --version')
  .command('init
')
  .alias('i')
  .description('输入项目名称,初始化项目模版')
  .action(async (projectName) => {
    const dir = path.join(process.cwd(), projectName);
    await checkDir(dir, projectName);
    const { url, val } = (await inquirer.prompt(promptTypeList)).type;
    const spinner = ora(`Loading ${chalk.red(val)} 仓库到项目中`).start();
    download(url, dir, { clone: true }, err => err ? spinner.fail('模板下载失败!') : spinner.succeed('模板下载成功!'));
  });

commander.parse(process.argv);

After creating the package, publish it with tnpm login , tnpm publish or npm publish . Users can then run youpin-cli init demo-cli to generate a project.

Conclusion

The article provides a beginner‑level guide to CLI scaffold development, covering required libraries, key package.json fields, and a full example that can be adapted for various front‑end projects.

CLINode.jscommanderinquirerpackage.jsonscaffoldingtooling
DaTaobao Tech
Written by

DaTaobao Tech

Official account of DaTaobao Technology

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.