命令行工具介绍及使用

195 阅读2分钟

常用工具

  • inquirerenquirerprompts:可以处理复杂的用户输入,完成命令行输入交互。
  • chalkkleur:使终端可以输出彩色信息文案。
  • ora:可以让命令行出现好看的 Spinners。
  • boxen:可以在命令行中画出 Boxes 区块。
  • listr:可以在命令行中画出进度列表。
  • meowarg:可以进行基础的命令行参数解析。
  • commanderyargs:可以进行更加复杂的命令行参数解析。
  • execa:允许开发中使用类似git的外部命令。
  • pkg-install:使用yarn installnpm install安装依赖。
  • download-git-repo:是一个用于从 Git 仓库下载代码仓库的 Node.js 模块。它允许您通过提供 Git 仓库的 URL,将代码下载到本地文件系统。

具体使用

cli.js

/** 
 * [template]:支持默认的几种模板类型,用户可以通过 select 进行选择。
 * --git:等同于git init去创建一个新的 Git 项目。
 * --install:支持自动下载项目依赖。
 * --yes:跳过命令行交互,直接使用默认配置。
*/

import arg from 'arg';
import inquirer from 'inquirer'
import { createProject } from './main'

// 解析命令行参数为 options
function parseArgumentsIntoOptions(rawArgs) {
  // 使用 arg 进行解析
  const args = arg(
    {
      '--git': Boolean,
      '--yes': Boolean,
      '--install': Boolean,
      '-g': '--git',
      '-y': '--yes',
      '-i': '--install',
    },
    {
      argv: rawArgs.slice(2),
    }
  )
  return {
    skipPrompts: args['--yes'] || false,
    git: args['--git'] || false,
    template: args._[0],
    runInstall: args['--install'] || false,
  }
}

async function promptForMissingOptions(options) {
  // 默认使用名为 JavaScript 的模板
  const defaultTemplate = 'JavaScript';
  // 使用默认模板则直接返回
  if (options.skipPrompts) {
    return {
      ...options,
      template: options.template || defaultTemplate,
    };
  }
  // 准备交互式问题 
  const questions = [];
  if (!options.template) {
    questions.push({
      type: 'list',
      name: 'template',
      message: 'Please choose which project template to use',
      choices: ['JavaScript', 'TypeScript'],
      default: defaultTemplate,
    });
  }
  if (!options.git) {
    questions.push({
      type: 'confirm',
      name: 'git',
      message: 'Initialize a git repository?',
      default: false,
    });
  }
  // 使用 inquirer 进行交互式查询,并获取用户答案选项
  const answers = await inquirer.prompt(questions);
  return {
    ...options,
    template: options.template || answers.template,
    git: options.git || answers.git,
  };
}

export async function cli(args) {
  
 // 获取命令行配置
 let options = parseArgumentsIntoOptions(args)
 options = await promptForMissingOptions(options)
 console.log(options)
 await createProject(options)
}

main.js

import chalk from 'chalk'
import fs from 'fs'
import ncp from 'ncp'
import path from 'path'
import { promisify } from 'util'
import execa from 'execa'
import Listr from 'listr'
import { projectInstall } from 'pkg-install'
const access = promisify(fs.access)
const copy = promisify(ncp)

// 拷贝模板
async function copyTemplateFiles(options) {
  return copy(options.templateDirectory, options.targetDirectory, {
    clobber: false,
  })
}

// 初始化 git
async function initGit(options) {
  // 执行 git init
  const result = await execa('git', ['init'], {
    cwd: options.targetDirectory,
  })
  if (result.failed) {
    return Promise.reject(new Error('Failed to initialize git'))
  }
  return
}

// 创建项目
export async function createProject(options) {
 options = {
    ...options,
    targetDirectory: options.targetDirectory || process.cwd()
 }

  const templateDir = path.resolve(new URL(import.meta.url).pathname,'../../templates',options.template)
  options.templateDirectory = templateDir

  try {
    // 判断模板是否存在
    await access(templateDir, fs.constants.R_OK)
  } catch (err) {
    console.error('%s Invalid template name', chalk.red.bold('ERROR'))
    process.exit(1)
  }

  // 声明 tasks
  const tasks = new Listr([
    {
      title: 'Copy project files',
      task: () => copyTemplateFiles(options),
    },
    {
      title: 'Initialize git',
      task: () => initGit(options),
      enabled: () => options.git,
    },
    {
      title: 'Install dependencies',
      task: () =>
        projectInstall({
          cwd: options.targetDirectory,
        }),
      skip: () =>
        !options.runInstall
          ? 'Pass --install to automatically install dependencies'
          : undefined,
    },
  ])
  // 并行执行 tasks
  await tasks.run()
  console.log('%s Project ready', chalk.green.bold('DONE'))
  return true
}