npm i lerna -g
lerna init
| 命令 | 功能 | 
|---|---|
| lerna bootstrap | 安装依赖 | 
| lerna clean | 删除各个包下的node_modules | 
| lerna init | 创建新的lerna库 | 
| lerna list | 查看本地包列表 | 
| lerna changed | 显示自上次release tag以来有修改的包, 选项通 list | 
| lerna diff | 显示自上次release tag以来有修改的包的差异, 执行 git diff | 
| lerna exec | 在每个包目录下执行任意命令 | 
| lerna run | 执行每个包package.json中的脚本命令 | 
| lerna add | 添加一个包的版本为各个包的依赖 | 
| lerna import | 引入package | 
| lerna link | 链接互相引用的库 | 
| lerna create | 新建package | 
| lerna publish | 发布 | 
{
  "name": "root",
  "private": true,
  "devDependencies": {
    "lerna": "^4.0.0"
  }
}
{
  "packages": [
    "packages/*"
  ],
  "version": "0.0.0"
}
node_modules
.DS_Store
design
*.log
packages/test
dist
temp
.vuerc
.version
.versions
.changelog
package.json
{
  "name": "root",
  "private": true,
+  "workspaces": [
+    "packages/*"
+  ],
  "devDependencies": {
    "lerna": "^4.0.0"
  }
}
lerna.json
{
  "packages": [
    "packages/*"
  ],
  "version": "1.0.0",
+ "useWorkspaces": true,
+ "npmClient": "yarn"
}
设置加速镜像
yarn config set registry http://registry.npm.taobao.org
 npm config set registry https://registry.npm.taobao.org
| 作用 | 命令 | 
|---|---|
| 查看工作空间信息 | yarn workspaces info | 
| 给根空间添加依赖 | yarn add chalk cross-spawn fs-extra --ignore-workspace-root-check | 
| 给某个项目添加依赖 | yarn workspace create-react-app3 add commander | 
| 删除所有的 node_modules | lerna clean 等于 yarn workspaces run clean | 
| 安装和link | yarn install 等于 lerna bootstrap --npm-client yarn --use-workspaces | 
| 重新获取所有的 node_modules | yarn install --force | 
| 查看缓存目录 | yarn cache dir | 
| 清除本地缓存 | yarn cache clean | 
lerna create zhang-cli
lerna create  zhang-cli-shared-utils
packages\zhang-cli\bin\vue.js
{
  "name": "zhang-cli",
  "version": "0.0.0",
  "description": "zhang-cli",
  "author": "zhangrenyang <zhang_renyang@126.com>",
  "homepage": "",
  "license": "MIT",
  "main": "bin/vue.js"
}
packages\zhang-cli\bin\vue.js
#!/usr/bin/env node
console.log('vue cli');
packages\zhang-cli-shared-utils\package.json
{
  "name": "zhang-cli-shared-utils",
  "version": "0.0.0",
  "description": "zhang-cli-shared-utils",
  "author": "zhangrenyang <zhang_renyang@126.com>",
  "homepage": "",
  "license": "MIT",
  "main": "index.js"
}
packages\zhang-cli-shared-utils\index.js
yarn
cd packages/zhang-cli
npm link
npm root -g
zhang-cli
{
  "name": "root",
  "private": true,
  "workspaces": [
    "packages/*"
  ],
  "devDependencies": {
    "lerna": "^4.0.0"
  },
  "scripts": {
+   "create": "node ./packages/zhang-cli/bin/vue.js create hello1"
  }
}
.vscode\launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "vue-cli",
            "cwd":"${workspaceFolder}",
            "runtimeExecutable": "npm",
            "runtimeArgs": [
                "run",
                "create"
            ],
            "port":9229,
            "autoAttachChildProcesses": true,
            "stopOnEntry": true,
            "skipFiles": [
                "<node_internals>/**"
            ]
        }
    ]
}
npm config set registry=https://registry.npm.taobao.org
yarn config set registry https://registry.npm.taobao.org
cd packages/zhang-cli-shared-utils
yarn workspace zhang-cli-shared-utils add  chalk execa
cd packages/zhang-cli
yarn workspace zhang-cli add  zhang-cli-shared-utils commander inquirer execa chalk ejs globby  lodash.clonedeep fs-extra ora isbinaryfile 
yarn用来处理依赖,lerna用于初始化和发布#!/usr/bin/env node
const program = require('commander');
program
    .version(`zhang-cli 0.0.0}`)
    .usage('<command> [options]')
program
    .command('create <app-name>')
    .description('create a new project powered by vue-cli-service')
    .action((name) => {
        console.log(name);
    })
program.parse(process.argv)
node 1.2.commander.js
Usage: 1.2.commander <command> [options]
Options:
  -V, --version      output the version number
  -h, --help         display help for command
Commands:
  create <app-name>  create a new project powered by vue-cli-service
  help [command]     display help for command
node 1.2.commander.js create hello  
const inquirer = require('inquirer')
const isManualMode = answers => answers.preset === '__manual__';
let defaultPreset = {
    useConfigFiles: false,
    cssPreprocessor: undefined,
    plugins: {
        '@vue/cli-plugin-babel': {},
        '@vue/cli-plugin-eslint': {
            config: 'base',
            lintOn: ['save']
        }
    }
}
let presets = {
    'default': Object.assign({ vueVersion: '2' }, defaultPreset),
    '__default_vue_3__': Object.assign({ vueVersion: '3' }, defaultPreset)
}
const presetChoices = Object.entries(presets).map(([name, preset]) => {
    let displayName = name
    if (name === 'default') {
        displayName = 'Default'
    } else if (name === '__default_vue_3__') {
        displayName = 'Default (Vue 3)'
    }
    return {
        name: `${displayName}`,
        value: name
    }
})
const presetPrompt = {
    name: 'preset',
    type: 'list',
    message: `Please pick a preset:`,
    choices: [
        ...presetChoices,
        {
            name: 'Manually select features',
            value: '__manual__'
        }
    ]
}
let features = [
    'vueVersion',
    'babel',
    'typescript',
    'pwa',
    'router',
    'vuex',
    'cssPreprocessors',
    'linter',
    'unit',
    'e2e'
];
const featurePrompt = {
    name: 'features',
    when: isManualMode,
    type: 'checkbox',
    message: 'Check the features needed for your project:',
    choices: features,
    pageSize: 10
}
const prompts = [
    presetPrompt,
    featurePrompt
]
;(async function(){
 let result = await inquirer.prompt(prompts);
 console.log(result);
})();
child_process.exec的封装const execa = require('execa');
(async () => {
    const {stdout} = await execa('echo', ['hello']);
    console.log(stdout);
})();
const chalk = require('chalk');
console.log(chalk.blue('Hello world!'));
template\main.js
<%_ if (rootOptions.vueVersion === '3') { _%>
  import { createApp } from 'vue'
  import App from './App.vue'
  createApp(App).mount('#app')
<%_ } else { _%>
  import Vue from 'vue'
  import App from './App.vue'
  Vue.config.productionTip = false
  new Vue({
    render: h => h(App),
  }).$mount('#app')
<%_ } _%>
doc\template\components
<template>
  <h1>HelloWorld</h1>
</template>
<script>
export default {
  name: 'HelloWorld'
}
</script>
doc\1.7.ejs.js
const path = require('path');
const fs = require('fs');
const ejs = require('ejs');
const globby = require('globby')
const slash = require('slash')
let source = path.join(__dirname, 'template');
;(async function () {
    const _files = await globby(['**/*'], { cwd: source })
    let files = {};
    for (const rawPath of _files) {
        const sourcePath = slash(path.resolve(source, rawPath))
        const template = fs.readFileSync(sourcePath, 'utf8')
        const content = ejs.render(template, {
            rootOptions: { vueVersion: '2' }
        })
        files[sourcePath] = content;
    }
    console.log(files);
})();
const path = require('path');
const { isBinaryFileSync } = require('isbinaryfile');
let logo = path.join(__dirname,'template/assets/logo.png');
let isBinary = isBinaryFileSync(logo);
console.log(isBinary);
let main = path.join(__dirname,'template/main.js');
isBinary = isBinaryFileSync(main);
console.log(isBinary);
const ora = require('ora')
const spinner = ora()
exports.logWithSpinner = (msg) => {
    spinner.text = msg
    spinner.start();
}
exports.stopSpinner = () => {
    spinner.stop();
}
exports.logWithSpinner('npm install');
setTimeout(()=>{
    exports.stopSpinner();
},3000);
vue-cli-service 注入命令。在项目创建的过程中,绝大部分列出的特性都是通过插件来实现的@vue/cli-plugin-eslint,社区插件vue-cli-plugin-apollo,指定的 scope 使用第三方插件@foo/vue-cli-plugin-barexports.defaultPreset = {
  useConfigFiles: false,
  cssPreprocessor: undefined,
  plugins: {
    '@vue/cli-plugin-babel': {},
    '@vue/cli-plugin-eslint': {
      config: 'base',
      lintOn: ['save']
    }
  }
}
packages\zhang-cli\bin\vue.js
#!/usr/bin/env node
const program = require('commander');
program
    .version(`@vue/zhang-cli ${require('../package').version}`)
    .usage('<command> [options]')
program
    .command('create <app-name>')
    .description('create a new project powered by vue-cli-service')
    .action((name) => {
        require('../lib/create')(name)
    })
program.parse(process.argv)
packages\zhang-\lib\create.js
const path = require('path');
async function create(projectName, options) {
  const cwd = process.cwd();
  const name = projectName;
  const targetDir = path.resolve(cwd, projectName);
  console.log(name);
  console.log(targetDir);
}
module.exports = (...args) => {
  return create(...args).catch(err => {
    console.log(err);
  })
}
packages\zhang-cli\lib\create.js
const path = require('path');
+const Creator = require('./Creator');
+const { getPromptModules } = require('./util/createTools')
async function create(projectName) {
  const cwd = process.cwd();
  const name = projectName;
  const targetDir = path.resolve(cwd, projectName);
+ const promptModules = getPromptModules();
+ const creator = new Creator(name, targetDir,promptModules);
+ await creator.create();
}
module.exports = (...args) => {
  return create(...args).catch(err => {
    console.log(err);
  })
}
packages\zhang-cli\lib\options.js
exports.defaultPreset = {
  useConfigFiles: false,
  cssPreprocessor: undefined,
  plugins: {
    '@vue/cli-plugin-babel': {},
    '@vue/cli-plugin-eslint': {
      config: 'base',
      lintOn: ['save']
    }
  }
}
exports.defaults = {
  presets: {
    'default': Object.assign({ vueVersion: '2' }, exports.defaultPreset),
    '__default_vue_3__': Object.assign({ vueVersion: '3' }, exports.defaultPreset)
  }
}
packages\zhang-cli\lib\PromptModuleAPI.js
class PromptModuleAPI {
    constructor(creator) {
        this.creator = creator
    }
    injectFeature(feature) {
        this.creator.featurePrompt.choices.push(feature)
    }
    injectPrompt(prompt) {
        this.creator.injectedPrompts.push(prompt)
    }
    onPromptComplete(cb) {
        this.creator.promptCompleteCbs.push(cb)
    }
}
module.exports = PromptModuleAPI;
packages\zhang-cli\lib\util\createTools.js
exports.getPromptModules = () => {
    return [
        'vueVersion'
    ].map(file => require(`../promptModules/${file}`))
}
packages\zhang-cli\lib\promptModules\vueVersion.js
module.exports = cli => {
  //cli.injectFeature 是注入 featurePrompt,即初始化项目时选择 babel,typescript,pwa 等等
  cli.injectFeature({
    name: 'Choose Vue version',
    value: 'vueVersion',
    description: 'Choose a version of Vue.js that you want to start the project with',
    checked: true
  })
  //cli.injectPrompt 是根据选择的 featurePrompt 然后注入对应的 prompt,当选择了 unit,接下来会有以下的 prompt,选择 Mocha + Chai 还是 Jest
  cli.injectPrompt({
    name: 'vueVersion',
    when: answers => answers.features.includes('vueVersion'),
    message: 'Choose a version of Vue.js that you want to start the project with',
    type: 'list',
    choices: [
      {
        name: '2.x',
        value: '2'
      },
      {
        name: '3.x',
        value: '3'
      }
    ],
    default: '2'
  })
  //cli.onPromptComplete 就是一个回调,会根据选择来添加对应的插件, 当选择了 mocha ,那么就会添加 @vue/cli-plugin-unit-mocha 插件
  cli.onPromptComplete((answers, options) => {
    if (answers.vueVersion) {
      options.vueVersion = answers.vueVersion
    }
  })
}
packages\zhang-cli\lib\Creator.js
const { defaults } = require('./options');
const PromptModuleAPI = require('./PromptModuleAPI');
const inquirer = require('inquirer')
const isManualMode = answers => answers.preset === '__manual__'
class Creator {
    constructor(name, context, promptModules) {
        this.name = name;
        this.context = process.env.VUE_CLI_CONTEXT = context;
        const { presetPrompt, featurePrompt } = this.resolveIntroPrompts();
        this.presetPrompt = presetPrompt;
        this.featurePrompt = featurePrompt;
        this.injectedPrompts = []
        this.promptCompleteCbs = []
        const promptAPI = new PromptModuleAPI(this)
        promptModules.forEach(m => m(promptAPI))
    }
    async create() {
        let preset = await this.promptAndResolvePreset()
        console.log('preset', preset);
    }
    resolveFinalPrompts() {
        this.injectedPrompts.forEach(prompt => {
            const originalWhen = prompt.when || (() => true)
            prompt.when = answers => {
                return isManualMode(answers) && originalWhen(answers)
            }
        })
        const prompts = [
            this.presetPrompt,
            this.featurePrompt,
            ...this.injectedPrompts,
        ]
        return prompts
    }
    async promptAndResolvePreset(answers = null) {
        if (!answers) {
            answers = await inquirer.prompt(this.resolveFinalPrompts())
        }
        let preset;
        if (answers.preset && answers.preset !== '__manual__') {
            preset = await this.resolvePreset(answers.preset)
        } else {
            preset = {
                plugins: {}
            }
            answers.features = answers.features || []
            this.promptCompleteCbs.forEach(cb => cb(answers, preset))
        }
        return preset
    }
    async resolvePreset (name) {
        const savedPresets = this.getPresets()
        return savedPresets[name];
    }
    getPresets() {
        return Object.assign({}, defaults.presets)
    }
    resolveIntroPrompts() {
        const presets = this.getPresets()
        const presetChoices = Object.entries(presets).map(([name]) => {
            let displayName = name
            if (name === 'default') {
                displayName = 'Default'
            } else if (name === '__default_vue_3__') {
                displayName = 'Default (Vue 3)'
            }
            return {
                name: `${displayName}`,
                value: name
            }
        })
        const presetPrompt = {
            name: 'preset',
            type: 'list',
            message: `Please pick a preset:`,
            choices: [
                ...presetChoices,
                {
                    name: 'Manually select features',
                    value: '__manual__'
                }
            ]
        }
        const featurePrompt = {
            name: 'features',
            when: isManualMode,
            type: 'checkbox',
            message: 'Check the features needed for your project:',
            choices: [],
            pageSize: 10
        }
        return {
            presetPrompt,
            featurePrompt
        }
    }
}
module.exports = Creator;
packages\zhang-cli-shared-utils\index.js
exports.chalk = require('chalk')
packages\zhang-cli\lib\Creator.js
const { defaults } = require('./options');
const PromptModuleAPI = require('./PromptModuleAPI');
const inquirer = require('inquirer')
+const cloneDeep = require('lodash.clonedeep')
+const writeFileTree = require('./util/writeFileTree')
+const { chalk } = require('zhang-cli-shared-utils')
const isManualMode = answers => answers.preset === '__manual__'
class Creator {
    constructor(name, context, promptModules) {
        this.name = name;
        this.context = process.env.VUE_CLI_CONTEXT = context;
        const { presetPrompt, featurePrompt } = this.resolveIntroPrompts();
        this.presetPrompt = presetPrompt;
        this.featurePrompt = featurePrompt;
        this.injectedPrompts = []
        this.promptCompleteCbs = []
        const promptAPI = new PromptModuleAPI(this)
        promptModules.forEach(m => m(promptAPI))
    }
    async create() {
+       const {name,context} = this;
        let preset = await this.promptAndResolvePreset()
        console.log('preset', preset);
+       preset = cloneDeep(preset);
+       preset.plugins['@vue/cli-service'] = Object.assign({projectName: name}, preset);
+       console.log(`✨  Creating project in ${chalk.yellow(context)}.`)
+       const pkg = {
+           name,
+           version: '0.1.0',
+           private: true,
+           devDependencies: {}
+       }
+       const deps = Object.keys(preset.plugins)
+       deps.forEach(dep => {
+           pkg.devDependencies[dep] = 'latest';
+       })
+       await writeFileTree(context, {
+           'package.json': JSON.stringify(pkg, null, 2)
+       })
    }
    resolveFinalPrompts() {
        this.injectedPrompts.forEach(prompt => {
            const originalWhen = prompt.when || (() => true)
            prompt.when = answers => {
                return isManualMode(answers) && originalWhen(answers)
            }
        })
        const prompts = [
            this.presetPrompt,
            this.featurePrompt,
            ...this.injectedPrompts,
        ]
        return prompts
    }
    async promptAndResolvePreset(answers = null) {
        if (!answers) {
            answers = await inquirer.prompt(this.resolveFinalPrompts())
        }
        let preset;
        if (answers.preset && answers.preset !== '__manual__') {
            preset = await this.resolvePreset(answers.preset)
        } else {
            preset = {
                plugins: {}
            }
            answers.features = answers.features || []
            this.promptCompleteCbs.forEach(cb => cb(answers, preset))
        }
        return preset
    }
    async resolvePreset (name) {
        const savedPresets = this.getPresets()
        return savedPresets[name];
    }
    getPresets() {
        return Object.assign({}, defaults.presets)
    }
    resolveIntroPrompts() {
        const presets = this.getPresets()
        const presetChoices = Object.entries(presets).map(([name]) => {
            let displayName = name
            if (name === 'default') {
                displayName = 'Default'
            } else if (name === '__default_vue_3__') {
                displayName = 'Default (Vue 3)'
            }
            return {
                name: `${displayName}`,
                value: name
            }
        })
        const presetPrompt = {
            name: 'preset',
            type: 'list',
            message: `Please pick a preset:`,
            choices: [
                ...presetChoices,
                {
                    name: 'Manually select features',
                    value: '__manual__'
                }
            ]
        }
        const featurePrompt = {
            name: 'features',
            when: isManualMode,
            type: 'checkbox',
            message: 'Check the features needed for your project:',
            choices: [],
            pageSize: 10
        }
        return {
            presetPrompt,
            featurePrompt
        }
    }
}
module.exports = Creator;
packages\zhang-cli\lib\util\writeFileTree.js
const fs = require('fs-extra')
const path = require('path')
module.exports = async function writeFileTree(dir, files) {
  Object.keys(files).forEach((name) => {
    const filePath = path.join(dir, name)
    fs.ensureDirSync(path.dirname(filePath))
    fs.writeFileSync(filePath, files[name])
  })
}
packages\zhang-cli\lib\Creator.js
const { defaults } = require('./options');
const PromptModuleAPI = require('./PromptModuleAPI');
const inquirer = require('inquirer')
const cloneDeep = require('lodash.clonedeep')
const writeFileTree = require('./util/writeFileTree')
+const { chalk, execa } = require('zhang-cli-shared-utils')
const isManualMode = answers => answers.preset === '__manual__'
class Creator {
    constructor(name, context, promptModules) {
        this.name = name;
        this.context = process.env.VUE_CLI_CONTEXT = context;
        const { presetPrompt, featurePrompt } = this.resolveIntroPrompts();
        this.presetPrompt = presetPrompt;
        this.featurePrompt = featurePrompt;
        this.injectedPrompts = []
        this.promptCompleteCbs = []
+       this.run = this.run.bind(this)//运行函数
        const promptAPI = new PromptModuleAPI(this)
        promptModules.forEach(m => m(promptAPI))
    }
+   run(command, args) {
+       return execa(command, args, { cwd: this.context })
+   }
    async create() {
+       const {name,context,run} = this;
        let preset = await this.promptAndResolvePreset()
        console.log('preset', preset);
        preset = cloneDeep(preset);
        preset.plugins['@vue/cli-service'] = Object.assign({projectName: name}, preset);
        console.log(`✨  Creating project in ${chalk.yellow(context)}.`)
        const pkg = {
            name,
            version: '0.1.0',
            private: true,
            devDependencies: {}
        }
        const deps = Object.keys(preset.plugins)
        deps.forEach(dep => {
            pkg.devDependencies[dep] = 'latest';
        })
        await writeFileTree(context, {
            'package.json': JSON.stringify(pkg, null, 2)
        })
+       console.log(`🗃  Initializing git repository...`)
+       await run('git init');
+       console.log(`⚙\u{fe0f} Installing CLI plugins. This might take a while...`)
+       await run('npm install');
    }
    resolveFinalPrompts() {
        this.injectedPrompts.forEach(prompt => {
            const originalWhen = prompt.when || (() => true)
            prompt.when = answers => {
                return isManualMode(answers) && originalWhen(answers)
            }
        })
        const prompts = [
            this.presetPrompt,
            this.featurePrompt,
            ...this.injectedPrompts,
        ]
        return prompts
    }
    async promptAndResolvePreset(answers = null) {
        if (!answers) {
            answers = await inquirer.prompt(this.resolveFinalPrompts())
        }
        let preset;
        if (answers.preset && answers.preset !== '__manual__') {
            preset = await this.resolvePreset(answers.preset)
        } else {
            preset = {
                plugins: {}
            }
            answers.features = answers.features || []
            this.promptCompleteCbs.forEach(cb => cb(answers, preset))
        }
        return preset
    }
    async resolvePreset (name) {
        const savedPresets = this.getPresets()
        return savedPresets[name];
    }
    getPresets() {
        return Object.assign({}, defaults.presets)
    }
    resolveIntroPrompts() {
        const presets = this.getPresets()
        const presetChoices = Object.entries(presets).map(([name]) => {
            let displayName = name
            if (name === 'default') {
                displayName = 'Default'
            } else if (name === '__default_vue_3__') {
                displayName = 'Default (Vue 3)'
            }
            return {
                name: `${displayName}`,
                value: name
            }
        })
        const presetPrompt = {
            name: 'preset',
            type: 'list',
            message: `Please pick a preset:`,
            choices: [
                ...presetChoices,
                {
                    name: 'Manually select features',
                    value: '__manual__'
                }
            ]
        }
        const featurePrompt = {
            name: 'features',
            when: isManualMode,
            type: 'checkbox',
            message: 'Check the features needed for your project:',
            choices: [],
            pageSize: 10
        }
        return {
            presetPrompt,
            featurePrompt
        }
    }
}
module.exports = Creator;
packages\zhang-cli\lib\Creator.js
const { defaults } = require('./options');
const PromptModuleAPI = require('./PromptModuleAPI');
const inquirer = require('inquirer')
const cloneDeep = require('lodash.clonedeep')
const writeFileTree = require('./util/writeFileTree')
+const { chalk, execa,loadModule } = require('zhang-cli-shared-utils')
+const Generator = require('./Generator')
const isManualMode = answers => answers.preset === '__manual__'
class Creator {
    constructor(name, context, promptModules) {
        this.name = name;
        this.context = process.env.VUE_CLI_CONTEXT = context;
        const { presetPrompt, featurePrompt } = this.resolveIntroPrompts();
        this.presetPrompt = presetPrompt;
        this.featurePrompt = featurePrompt;
        this.injectedPrompts = []
        this.promptCompleteCbs = []
        this.run = this.run.bind(this)//运行函数
        const promptAPI = new PromptModuleAPI(this)
        promptModules.forEach(m => m(promptAPI))
    }
    run(command, args) {
        return execa(command, args, { cwd: this.context })
    }
    async create() {
        const {name,context,run} = this;
        let preset = await this.promptAndResolvePreset()
        console.log('preset', preset);
        preset = cloneDeep(preset);
        preset.plugins['@vue/cli-service'] = Object.assign({projectName: name}, preset);
        console.log(`✨  Creating project in ${chalk.yellow(context)}.`)
        const pkg = {
            name,
            version: '0.1.0',
            private: true,
            devDependencies: {}
        }
        const deps = Object.keys(preset.plugins)
        deps.forEach(dep => {
            pkg.devDependencies[dep] = 'latest';
        })
        await writeFileTree(context, {
            'package.json': JSON.stringify(pkg, null, 2)
        })
        console.log(`🗃  Initializing git repository...`)
        await run('git init');
        console.log(`⚙\u{fe0f} Installing CLI plugins. This might take a while...`)
        await run('npm install');
+       console.log(`🚀  Invoking generators...`)
+       const plugins = await this.resolvePlugins(preset.plugins)
+       const generator = new Generator(context, {pkg,plugins})
+       await generator.generate();
    }
+   async resolvePlugins(rawPlugins) {
+       const plugins = []
+       for (const id of Object.keys(rawPlugins)) {
+           try{
+               const apply = loadModule(`${id}/generator`, this.context) || (() => {})
+               let options = rawPlugins[id] || {}
+               plugins.push({ id, apply, options })
+           }catch(error){
+               console.log(error);
+           } 
+       }
+       return plugins
+   }
    resolveFinalPrompts() {
        this.injectedPrompts.forEach(prompt => {
            const originalWhen = prompt.when || (() => true)
            prompt.when = answers => {
                return isManualMode(answers) && originalWhen(answers)
            }
        })
        const prompts = [
            this.presetPrompt,
            this.featurePrompt,
            ...this.injectedPrompts,
        ]
        return prompts
    }
    async promptAndResolvePreset(answers = null) {
        if (!answers) {
            answers = await inquirer.prompt(this.resolveFinalPrompts())
        }
        let preset;
        if (answers.preset && answers.preset !== '__manual__') {
            preset = await this.resolvePreset(answers.preset)
        } else {
            preset = {
                plugins: {}
            }
            answers.features = answers.features || []
            this.promptCompleteCbs.forEach(cb => cb(answers, preset))
        }
        return preset
    }
    async resolvePreset (name) {
        const savedPresets = this.getPresets()
        return savedPresets[name];
    }
    getPresets() {
        return Object.assign({}, defaults.presets)
    }
    resolveIntroPrompts() {
        const presets = this.getPresets()
        const presetChoices = Object.entries(presets).map(([name]) => {
            let displayName = name
            if (name === 'default') {
                displayName = 'Default'
            } else if (name === '__default_vue_3__') {
                displayName = 'Default (Vue 3)'
            }
            return {
                name: `${displayName}`,
                value: name
            }
        })
        const presetPrompt = {
            name: 'preset',
            type: 'list',
            message: `Please pick a preset:`,
            choices: [
                ...presetChoices,
                {
                    name: 'Manually select features',
                    value: '__manual__'
                }
            ]
        }
        const featurePrompt = {
            name: 'features',
            when: isManualMode,
            type: 'checkbox',
            message: 'Check the features needed for your project:',
            choices: [],
            pageSize: 10
        }
        return {
            presetPrompt,
            featurePrompt
        }
    }
}
module.exports = Creator;
packages\zhang-cli-shared-utils\index.js
+['pluginResolution','module'].forEach(m => {
+    Object.assign(exports, require(`./lib/${m}`))
+})
exports.chalk = require('chalk')
exports.execa = require('execa')
packages\zhang-cli-shared-utils\lib\module.js
const Module = require('module')
const path = require('path')
exports.loadModule = function (request, context) {
    return Module.createRequire(path.resolve(context, 'package.json'))(request)
}
packages\zhang-cli-shared-utils\lib\pluginResolution.js
const pluginRE = /^@vue\/cli-plugin-/
exports.toShortPluginId = id => id.replace(pluginRE, '');//@vue/cli-plugin-babel => babel
exports.isPlugin = id => pluginRE.test(id)
exports.matchesPluginId = (input, full) => {
    return full === input;
}
packages\zhang-cli\lib\util\mergeDeps.js
function mergeDeps(sourceDeps, depsToInject) {
  const result = Object.assign({}, sourceDeps)
  for (const depName in depsToInject) {
    result[depName] = depsToInject[depName];
  }
  return result
}
module.exports = mergeDeps;
packages\zhang-cli\lib\util\normalizeFilePaths.js
const slash = require('slash')
module.exports = function normalizeFilePaths (files) {
  Object.keys(files).forEach(file => {
    const normalized = slash(file)
    if (file !== normalized) {
      files[normalized] = files[file]
      delete files[file]
    }
  })
  return files
}
packages\zhang-cli\lib\GeneratorAPI.js
const { toShortPluginId } = require('zhang-cli-shared-utils')
const mergeDeps = require('./util/mergeDeps')
const { isBinaryFileSync } = require('isbinaryfile')
const isString = val => typeof val === 'string'
const isObject = val => val && typeof val === 'object'
const path = require('path');
const fs = require('fs');
const ejs = require('ejs');
class GeneratorAPI {
    constructor(id, generator, options, rootOptions) {
        this.id = id
        this.generator = generator
        this.options = options
        this.rootOptions = rootOptions
        this.pluginsData = generator.plugins
            .filter(({ id }) => id !== `@vue/cli-service`)
            .map(({ id }) => ({ name: toShortPluginId(id) }))
    }
    hasPlugin(id) {
        return this.generator.hasPlugin(id)
    }
    extendPackage(fields) {
        const pkg = this.generator.pkg
        const toMerge = fields
        for (const key in toMerge) {
            const value = toMerge[key]
            const existing = pkg[key]
            if (isObject(value) && (key === 'dependencies' || key === 'devDependencies')) {
                pkg[key] = mergeDeps(existing || {}, value)
            } else {
                pkg[key] = value
            }
        }
    }
    _injectFileMiddleware(middleware) {
        this.generator.fileMiddlewares.push(middleware)
    }
    _resolveData(additionalData) {
        return Object.assign({
            options: this.options,
            rootOptions: this.rootOptions,
            plugins: this.pluginsData
          }, additionalData)
    }
    render(source,additionalData) {
        const baseDir = extractCallDir()
        if (isString(source)) {
            source = path.resolve(baseDir, source)
            this._injectFileMiddleware(async (files) => {
                const data = this._resolveData(additionalData)
                const globby = require('globby')
                const _files = await globby(['**/*'], { cwd: source })
                for (const rawPath of _files) {
                    const targetPath = rawPath.split('/').map(filename => {
                        if (filename.charAt(0) === '_' && filename.charAt(1) !== '_') {
                            return `.${filename.slice(1)}`
                        }
                        return filename
                    }).join('/')
                    const sourcePath = path.resolve(source, rawPath)
                    const content = renderFile(sourcePath, data)
                    files[targetPath] = content;
                }
            })
        }
    }
}
function extractCallDir() {
    const obj = {}
    Error.captureStackTrace(obj)
    const callSite = obj.stack.split('\n')[3]
    const namedStackRegExp = /\s\((.*):\d+:\d+\)$/
    let matchResult = callSite.match(namedStackRegExp)
    const fileName = matchResult[1]
    return path.dirname(fileName)
}
function renderFile(name, data) {
    if (isBinaryFileSync(name)) {
        return fs.readFileSync(name)
    }
    const template = fs.readFileSync(name, 'utf8')
    return ejs.render(template, data)
}
module.exports = GeneratorAPI
packages\zhang-cli\lib\Generator.js
const { isPlugin,matchesPluginId } = require('zhang-cli-shared-utils')
const GeneratorAPI = require('./GeneratorAPI')
const normalizeFilePaths = require('./util/normalizeFilePaths')
const writeFileTree = require('./util/writeFileTree')
const ejs = require('ejs')
class Generator {
    constructor(context, { pkg = {}, plugins = [] } = {}) {
        this.context = context
        this.plugins = plugins
        this.files = {}
        this.fileMiddlewares = []
        this.pkg = pkg;
        this.allPluginIds = Object.keys(this.pkg.dependencies || {})
            .concat(Object.keys(this.pkg.devDependencies || {}))
            .filter(isPlugin)
        const cliService = plugins.find(p => p.id === '@vue/cli-service')
        this.rootOptions = cliService.options;
    }
    async generate() {
        await this.initPlugins()
        //将一些配置信息从package.json中提取到单独的文件中,比如postcss.config.js babel.config.js
        this.extractConfigFiles()
        //遍历fileMiddleware,向files里写入文件,并插入import和rootOptions
        await this.resolveFiles()
        console.log(this.files);
        this.sortPkg()
        this.files['package.json'] = JSON.stringify(this.pkg, null, 2) + '\n'
        //把内存中的文件写入硬盘
        await writeFileTree(this.context, this.files)
    }
    sortPkg() {
        console.log('ensure package.json keys has readable order');
    }
    async resolveFiles() {
        const files = this.files
        for (const middleware of this.fileMiddlewares) {
            await middleware(files, ejs.render)
        }
        normalizeFilePaths(files)
    }
    extractConfigFiles() {
        console.log('extractConfigFiles');
    }
    async initPlugins() {
        const { rootOptions } = this
        for (const plugin of this.plugins) {
            const { id, apply, options } = plugin
            const api = new GeneratorAPI(id, this, options, rootOptions)
            await apply(api, options, rootOptions)
        }
    }
    hasPlugin(_id) {
        return [
          ...this.plugins.map(p => p.id),
          ...this.allPluginIds
        ].some(id => {
          return matchesPluginId(_id, id);
        })
      }
}
module.exports = Generator;
packages\zhang-cli\lib\Creator.js
const { defaults } = require('./options');
const PromptModuleAPI = require('./PromptModuleAPI');
const inquirer = require('inquirer')
const cloneDeep = require('lodash.clonedeep')
const writeFileTree = require('./util/writeFileTree')
const { chalk, execa,loadModule } = require('zhang-cli-shared-utils')
const Generator = require('./Generator')
const isManualMode = answers => answers.preset === '__manual__'
class Creator {
    constructor(name, context, promptModules) {
        this.name = name;
        this.context = process.env.VUE_CLI_CONTEXT = context;
        const { presetPrompt, featurePrompt } = this.resolveIntroPrompts();
        this.presetPrompt = presetPrompt;
        this.featurePrompt = featurePrompt;
        this.injectedPrompts = []
        this.promptCompleteCbs = []
        this.run = this.run.bind(this)//运行函数
        const promptAPI = new PromptModuleAPI(this)
        promptModules.forEach(m => m(promptAPI))
    }
    run(command, args) {
        return execa(command, args, { cwd: this.context })
    }
    async create() {
        const {name,context,run} = this;
        let preset = await this.promptAndResolvePreset()
        console.log('preset', preset);
        preset = cloneDeep(preset);
        preset.plugins['@vue/cli-service'] = Object.assign({projectName: name}, preset);
        console.log(`✨  Creating project in ${chalk.yellow(context)}.`)
        const pkg = {
            name,
            version: '0.1.0',
            private: true,
            devDependencies: {}
        }
        const deps = Object.keys(preset.plugins)
        deps.forEach(dep => {
            pkg.devDependencies[dep] = 'latest';
        })
        await writeFileTree(context, {
            'package.json': JSON.stringify(pkg, null, 2)
        })
        console.log(`🗃  Initializing git repository...`)
        await run('git init');
        console.log(`⚙\u{fe0f} Installing CLI plugins. This might take a while...`)
        await run('npm install');
        console.log(`🚀  Invoking generators...`)
        const plugins = await this.resolvePlugins(preset.plugins)
        const generator = new Generator(context, {pkg,plugins})
        await generator.generate();
+       console.log(`📦  Installing additional dependencies...`)
+       await run('npm install');
+       console.log('📄  Generating README.md...')
+       await writeFileTree(context, {
+           'README.md': `cd ${name}\n npm run serve`
+       })
+       await run('git', ['add', '-A'])
+       await run('git', ['commit', '-m', 'created', '--no-verify'])
+       console.log(`🎉  Successfully created project ${chalk.yellow(name)}.`)
+       console.log(
+           `👉  Get started with the following commands:\n\n` +
+           (chalk.cyan(`cd ${name}\n`)) +
+           (chalk.cyan(`npm run serve`))
+       )
+       generator.printExitLogs()
    }
    //遍历插件的generator,插件通过GeneratorAPI向package.json中加入依赖或字段,并通过render准备添加文件
    async resolvePlugins(rawPlugins) {
        const plugins = []
        for (const id of Object.keys(rawPlugins)) {
            try{
                const apply = loadModule(`${id}/generator`, this.context) || (() => {})
                let options = rawPlugins[id] || {}
                plugins.push({ id, apply, options })
            }catch(error){
                console.log(error);
            } 
        }
        return plugins
    }
    resolveFinalPrompts() {
        this.injectedPrompts.forEach(prompt => {
            const originalWhen = prompt.when || (() => true)
            prompt.when = answers => {
                return isManualMode(answers) && originalWhen(answers)
            }
        })
        const prompts = [
            this.presetPrompt,
            this.featurePrompt,
            ...this.injectedPrompts,
        ]
        return prompts
    }
    async promptAndResolvePreset(answers = null) {
        if (!answers) {
            answers = await inquirer.prompt(this.resolveFinalPrompts())
        }
        let preset;
        if (answers.preset && answers.preset !== '__manual__') {
            preset = await this.resolvePreset(answers.preset)
        } else {
            preset = {
                plugins: {}
            }
            answers.features = answers.features || []
            this.promptCompleteCbs.forEach(cb => cb(answers, preset))
        }
        return preset
    }
    async resolvePreset (name) {
        const savedPresets = this.getPresets()
        return savedPresets[name];
    }
    getPresets() {
        return Object.assign({}, defaults.presets)
    }
    resolveIntroPrompts() {
        const presets = this.getPresets()
        const presetChoices = Object.entries(presets).map(([name]) => {
            let displayName = name
            if (name === 'default') {
                displayName = 'Default'
            } else if (name === '__default_vue_3__') {
                displayName = 'Default (Vue 3)'
            }
            return {
                name: `${displayName}`,
                value: name
            }
        })
        const presetPrompt = {
            name: 'preset',
            type: 'list',
            message: `Please pick a preset:`,
            choices: [
                ...presetChoices,
                {
                    name: 'Manually select features',
                    value: '__manual__'
                }
            ]
        }
        const featurePrompt = {
            name: 'features',
            when: isManualMode,
            type: 'checkbox',
            message: 'Check the features needed for your project:',
            choices: [],
            pageSize: 10
        }
        return {
            presetPrompt,
            featurePrompt
        }
    }
}
module.exports = Creator;
packages\zhang-cli\lib\Generator.js
const { isPlugin,matchesPluginId } = require('zhang-cli-shared-utils')
const GeneratorAPI = require('./GeneratorAPI')
const normalizeFilePaths = require('./util/normalizeFilePaths')
const writeFileTree = require('./util/writeFileTree')
const ejs = require('ejs')
class Generator {
    constructor(context, { pkg = {}, plugins = [] } = {}) {
        this.context = context
        this.plugins = plugins
        this.files = {}
        this.fileMiddlewares = []
        this.pkg = pkg;
        this.allPluginIds = Object.keys(this.pkg.dependencies || {})
            .concat(Object.keys(this.pkg.devDependencies || {}))
            .filter(isPlugin)
        const cliService = plugins.find(p => p.id === '@vue/cli-service')
        this.rootOptions = cliService.options;
    }
    async generate() {
        await this.initPlugins()
        this.extractConfigFiles()
        await this.resolveFiles()
        this.sortPkg()
        this.files['package.json'] = JSON.stringify(this.pkg, null, 2) + '\n'
        await writeFileTree(this.context, this.files)
    }
    sortPkg() {
        console.log('ensure package.json keys has readable order');
    }
    async resolveFiles() {
        const files = this.files
        for (const middleware of this.fileMiddlewares) {
            await middleware(files, ejs.render)
        }
        normalizeFilePaths(files)
    }
    extractConfigFiles() {
        console.log('extractConfigFiles');
    }
    async initPlugins() {
        const { rootOptions } = this
        for (const plugin of this.plugins) {
            const { id, apply, options } = plugin
            const api = new GeneratorAPI(id, this, options, rootOptions)
            await apply(api, options, rootOptions)
        }
    }
    hasPlugin(_id) {
        return [
          ...this.plugins.map(p => p.id),
          ...this.allPluginIds
        ].some(id => {
          return matchesPluginId(_id, id);
        })
    }
+    printExitLogs(){
+        console.log('printExitLogs');
+    }
}
module.exports = Generator;