项目地址
执行命令: wangling-vue create 项目名称
0、相关的插件
- “chalk”: “^4.1.2”, 主要是用来颜色变化的
- “commander”: “^6.0.0”, 配置命令行
- “fs-extra”: “^10.0.0”,
- fs.pathExistsSync(fileAddr):目录是否存在
- fs.remove(fileAddr): 删除目录
- “inquirer”: “^8.2.0” 提示交互 ,带选择的
希望使用 wangling create vue来创建脚手架
2. 配置package.json的bin字段
```json
// 这种写法的话 执行命令 必须与name字段一致,必须要 wangling-vue才会执行
{
"name": "wangling-vue",
"version": "1.0.0",
"description": "自定义vue脚手架",
"main": "index.js",
"bin": "./bin/wangling",
}
// 第二种写法 可以不跟name一样
{
"name": "wangling-vue-cli",
"version": "1.0.0",
"description": "自定义vue脚手架",
"main": "index.js",
"bin": {
"wangling-vue": "./bin/wangling"
}
}
把包临时放到全局下
npm link链接到本地环境中npm link // 如果已经链接过了会报错,可以使用这个命令强制绑定 npm link --force
包叫wangling-vue-cli
它指向的是npm\node_modules\wangling-vue-cli\bin\wangling运行
注意:默认运行命令与package.json的name一致才可以
wangling-vue
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
}