搭建自己的脚手架

为什么要搭建cli

在前端开发中,在搭建好一套自己满意的代码架子后,希望以后可以在别的开发中也一直沿用。之前或许把这份代码的结构再手动码一份,但这样的重复劳动其实搭建好自己的cli后,就可以通过几个命令来完成了

必备模块

"dependencies": {
  "axios": "^0.19.2",
  "chalk": "^4.1.0",
  "commander": "^6.0.0",
  "download-git-repo": "^3.0.2",
  "fs-extra": "^9.0.1",
  "inquirer": "^7.3.3",
  "ora": "^5.0.0"
}

步骤

  • npm link

使用npm link将yh-cli链接到全局

  • commander
安装及使用
// 安装
npm i commander
// 使用
const program = require('commander');
program.parse(process.argv);
命令配置
program
    .command(create'')  //配置命令的名字
    .alias('c') // 配置命令的参数
    .description('TyrionJYQ personal CLI')  // 命令描述
    .action(() => {
        console.log('TyrionJYQ') // 命令动作
    })

在创建多个命令时,可以先将需要设置的命令用对象来描述,然后遍历对象,调用program生成命令

module.exports = {
  create: {
    alias: 'c',
    description: 'create a new project',
    examples: [
      'tj-cli create <project-name>',
    ],
  },
  config: {
    alias: 'conf',
    description: 'config project varible',
    examples: [
      'tj-cli config set <key> <value>',
      'tj-cli config get <key>',
    ],
  },
  '*': {
    alias: '',
    description: 'command not found',
    examples: [],
  },
}
  • 下载模板

使用axios下载模板

// 通过axios来获取结果
const axios = require('axios');

axios.interceptors.response.use(res => res.data);

async function fetchRepoList () {
  return axios.get('组织仓库地址');
}

async function fetchTagList (repo) {
  return axios.get(`版本号地址`);
}

module.exports = {
  fetchRepoList,
  fetchTagList
}
加载动画

使用ora显示加载动画
安装

npm install ora

使用

const spinner = ora('download...')
spinner.start()  //开启动画
spinner.succeed() // 关闭动画
问询(交互)

安装

npm install inquirer

使用

const { repo } = await Inquirer({   
    name: 'repo',
    type: 'list',
    message: 'choice a repo',
    choices: repos  
})

问询是一个等待用户选择,所以这是一个异步事件

实现效果

实现效果

目录结构

目录结构

代码实现

  • yh
#! /usr/bin/env node
// 创建可执行命令
const program = require('commander');
// 修改命令在控制台颜色
const chalk = require('chalk');

// 提取cmd中的属性
const cleanArgs = cmd => {
  const args = {};
  cmd.options.forEach(o => {
    const key = o.long.slice(2);
    cmd[key] && (args[key] = cmd[key]);
  })
  return args;
}

// 创建项目
program
  .command('create <app-name>')
  .description('create a new project')
  .option('-f, --force', 'overwrite target directory if it exists')
  .action((name, cmd) => {
    // 调用create模块去创建
    require('../lib/create')(name, cleanArgs(cmd));
  })

// 监听 "--help命令输入"
program.on('--help', function () {
  console.log();
  console.log(`Run ${chalk.cyan(`yahang-cli <command> --help`)} show details`)
  console.log();
})

program.parse(process.argv);
  • create.js
const path = require('path');
// node 自带的 fs并不能返回promise 所以此处使用 fs-extrafs-extra
const fs = require('fs-extra');
// 创建交互式命令
const Inquirer = require('inquirer');
const Creator = require('./Ctreator');
// 创建项目
module.exports = async function (projectName, options) {
  // 获取当前命令执行时的工作目录
  const cwd = process.cwd();
  // 目标目录
  const targetDir = path.join(cwd, projectName);
  if (fs.existsSync(targetDir)) {
    // 如果强制创建 ,删除已有的
    if (options.force) await fs.remove(targetDir);
    else {
      // 提示用户是否确定要覆盖
      let { action } = await Inquirer.prompt([ // 配置询问的方式
        {
          name: 'action',
          type: 'list', // 类型非常丰富
          message: `Target directory already exists Pick an action:`,
          choices: [
            { name: 'Overwrite', value: 'overwrite' },
            { name: 'Cancel', value: false }
          ]
        }
      ]);
      if (!action) return
      else if (action === 'overwrite') {
        console.log(`\r\nRemoving....`);
        await fs.remove(targetDir)
      }
    }
  }
  // 创建项目
  const creator = new Creator(projectName, targetDir);
  creator.create(); // 开始创建项目 
}
  • Ctreator.js
const { fetchRepoList, fetchTagList } = require("./request");
const Inquirer = require('inquirer');
const { wrapLoading } = require('./util');
const downloadGitRepo = require('download-git-repo'); // 不支持promise
const util = require('util');
const path = require('path');
class Creator {
  constructor(projectName, targetDir) {
    this.name = projectName;
    this.target = targetDir;
    // 此时这个方法就是一个promise方法了
    this.downloadGitRepo = util.promisify(downloadGitRepo);
  }
  async fetchRepo () {
    // 失败重新拉取 
    let repos = await wrapLoading(fetchRepoList, 'waiting fetch template');
    if (!repos) return;
    repos = repos.map(item => item.name);
    let { repo } = await Inquirer.prompt({
      name: 'repo',
      type: 'list',
      choices: repos,
      message: 'please choose a template to create project'
    });
    return repo
  }
  async fetchTag (repo) {
    let tags = await wrapLoading(fetchTagList, 'waiting fetch tag', repo);
    if (!tags) return;
    tags = tags.map(item => item.name);
    let { tag } = await Inquirer.prompt({
      name: 'tag',
      type: 'list',
      choices: tags,
      message: 'please choose a tag to create project'
    });
    return tag;
  }
  async download (repo, tag) {
    // 1.需要拼接处下载路径来 
    let requestUrl = `项目名/${repo}${tag ? '#' + tag : ''}`
    // 2.把资源下载到某个路径上
    await wrapLoading(this.downloadGitRepo, 'waiting donwload', requestUrl, path.resolve(process.cwd(), `${repo}@${tag}`));
    return this.target;
  }
  async create () {
    // 真实开始创建了
    // 1) 先去拉取当前组织下的模板
    let repo = await this.fetchRepo();
    // 2) 在通过模板找到版本号
    let tag = await this.fetchTag(repo);
    // 3) 下载
    await this.download(repo, tag);
  }
}

module.exports = Creator;
  • request.js
// 通过axios来获取结果
const axios = require('axios');

axios.interceptors.response.use(res => res.data);

async function fetchRepoList () {
  return axios.get('组织仓库地址');
}

async function fetchTagList (repo) {
  return axios.get(`代码地址`);
}

module.exports = {
  fetchRepoList,
  fetchTagList
}
  • util.js
// 命令行加载
const ora = require('ora');
async function sleep (n) {
  return new Promise(resolve => setTimeout(resolve, n));
}
// 制作了一个等待的loading
async function wrapLoading (fn, message, ...args) {
  const spinner = ora(message);
  //开启加载
  spinner.start(); 
  try {
    let repos = await fn(...args);
    spinner.succeed();
    return repos;
  } catch (e) {
    spinner.fail('request failed , refetch...');
    await sleep(1000);
    return wrapLoading(fn, message, ...args);
  }
}
module.exports = {
  sleep,
  wrapLoading
}
  • package.json
{
  "author": "yh",
  "bin": {
    "yh": "./bin/yh",
    "yahang": "./bin/yh"
  },
  "dependencies": {
    "axios": "^0.19.2",
    "chalk": "^4.1.0",
    "commander": "^6.0.0",
    "download-git-repo": "^3.0.2",
    "fs-extra": "^9.0.1",
    "inquirer": "^7.3.3",
    "ora": "^5.0.0"
  },
  "keywords": [],
  "license": "ISC",
  "main": "index.js",
  "name": "yahang-cli",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "version": "1.0.0"
}
运行
# yh create xxx
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容