项目地址
执行命令: wangling-vue create 项目名称

0、相关的插件

  • “chalk”: “^4.1.2”, 主要是用来颜色变化的

image.png

  • “commander”: “^6.0.0”, 配置命令行

image.png

  • “fs-extra”: “^10.0.0”,
    • fs.pathExistsSync(fileAddr):目录是否存在
    • fs.remove(fileAddr): 删除目录
  • “inquirer”: “^8.2.0” 提示交互 ,带选择的

image.png

  • download-git-repo:一个用于下载git仓库的项目的模块
  • ora: 这个模块用于在终端里有显示载入动画

    1、将包变成全局的

希望使用 wangling create vue来创建脚手架

  1. 创建可执行的脚本
    bin/wangling ```javascript

    ! /usr/bin/env node

    console.log(‘执行了’)
  1. 2. 配置package.jsonbin字段
  2. ```json
  3. // 这种写法的话 执行命令 必须与name字段一致,必须要 wangling-vue才会执行
  4. {
  5. "name": "wangling-vue",
  6. "version": "1.0.0",
  7. "description": "自定义vue脚手架",
  8. "main": "index.js",
  9. "bin": "./bin/wangling",
  10. }
  11. // 第二种写法 可以不跟name一样
  12. {
  13. "name": "wangling-vue-cli",
  14. "version": "1.0.0",
  15. "description": "自定义vue脚手架",
  16. "main": "index.js",
  17. "bin": {
  18. "wangling-vue": "./bin/wangling"
  19. }
  20. }
  1. 把包临时放到全局下
    npm link链接到本地环境中

    npm link
    // 如果已经链接过了会报错,可以使用这个命令强制绑定
    npm link --force
    

    包叫wangling-vue-cli
    它指向的是npm\node_modules\wangling-vue-cli\bin\wangling
    image.png

  2. 运行

注意:默认运行命令与package.json的name一致才可以

wangling-vue

image.png

2、命令行交互(ccommander)

npm i commander@6.0.0 -S
#! /usr/bin/env node

const program = require('commander')
// 命令行添加颜色
const chalk = require('chalk')

const cleanArgs = cmd => {
  const args = {}
  cmd.options.forEach(o => {
    const key = o.long.slice(2)
    if (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) => {
    require('../lib/create')(name, cleanArgs(cmd))
  })

/*
* 比如在vue脚手架里
* vue config --set a 1 => 配置文件中 设置a = 1
* vue config --get a => 获取某个属性的值
* */
program
  .command('config [value]')
  .description('inspect and modify the config')
  .option('-g, --get <path>', '获取配置')
  .option('-s, --set <path> <value>', '设置某一项配置')
  .option('-d, --delete <path>', '删除某一项配置')
  .action((value, cmd) => {
    console.log(value, cleanArgs(cmd))
  })

program
  .command('ui')
  .description('start and open wangling-cli ui')
  .option('-p, --port <port>', '设置端口号 wangling-vue ui -p <port>')
  .action((cmd) => {
    console.log(cleanArgs(cmd))
  })

program
  .on('--help', function() {
    console.log()
    console.log(`Run ${chalk.blue('wangling-vue <command> --help')} show details`)
    console.log()
  })

program
  .usage(`<command> [option]`)
  .version(require('../package.json').version, '-V', 'display version')
  .parse(process.argv)

3、创建项目

// create.js
// 创建项目
const path = require('path')
const fs = require('fs-extra')
const Inquirer = require('inquirer')
const Creator = require('./Creator')
module.exports = async function(projectName, options) {
  const cwd = process.cwd() // 获取当前命令执行时的工作目录
  const targetDir = path.join(cwd, projectName)
  // 判断当前的创建的项目名是否已经存在
  if (fs.pathExistsSync(targetDir)) {
    if (options.force) { // 如果强制创建,则先删除目录
      await fs.remove(targetDir)
    } else {
      // 提示用户是否确定要覆盖
      const { action } = await Inquirer.prompt([ // 配置询问方式
        {
          name: 'action',
          type: 'list',
          message: '当前的目录已经存在了,请选择:',
          choices: [
            {
              name: '覆盖',
              value: true
            },
            {
              name: '取消',
              value: false
            }
          ]
        }
      ])
      if (!action) {
        return
      }
      console.log('删除中...')
      await fs.remove(targetDir)
    }
  }
  // 创建项目
  const creator = new Creator(projectName, targetDir)
  creator.create()
}
// Creator
/*
   * 采用远程拉取
   * api.github.com/repos/zhu-cli/vue-template/tags
   * api.github.com/orgs/zhu-cli/repos
   * */
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 : ''}`
    // zhu-cli/vue-template#1.0
    let requestUrl = `zhu-cli/${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
// 通过axios来获取结果
const axios = require('axios');

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

async function fetchRepoList () {
  // return axios.get('组织仓库地址');
  // 可以通过配置文件,拉取不同仓库对应用户下的文件
  return axios.get('https://api.github.com/orgs/zhu-cli/repos')
}

async function fetchTagList (repo) {
  // return axios.get(`代码地址`);
  return axios.get(`https://api.github.com/repos/zhu-cli/${ repo }/tags`)
}

module.exports = {
  fetchRepoList,
  fetchTagList
}
// util
// 命令行加载
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
}