UNPKG

zoro-cli

Version:

https://github.com/vuejs/vue-cli

381 lines (308 loc) 11 kB
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } const debug = require('zoro-cli-util/debug')('cli-creator'); const { importFrom } = require('zoro-cli-util/import'); const { gitNotClean, ensureGitClean } = require('zoro-cli-util/git'); const run = require('zoro-cli-util/run'); const { log, info, warn, success, stopSpinner } = require('zoro-cli-util/logger'); const { removeFiles } = require('zoro-cli-util/fs'); const { installDeps } = require('zoro-cli-util/pkg'); const { isFunction } = require('zoro-cli-util/is'); const inquirer = require('inquirer'); const fs = require('fs-extra'); const path = require('path'); const cloneDeep = require('lodash/cloneDeep'); const chalk = require('chalk'); const Generator = require('./Generator'); const Types = require('./types'); const PromptAPI = require('./PromptAPI'); async function shouldReConfig() { const { reConfig } = await inquirer.prompt([{ type: 'list', name: 'reConfig', message: '是否重新配置', default: true, choices: [{ name: 'Yes, 重新配置 (会自动读取以前的配置作为默认值, 如果你的项目距离上次配置过了很久, 推荐重新配置)', value: true, short: 'Yes' }, { name: 'No, 不用配置了', value: false, short: 'No' }] }]); return reConfig; } class Creator { constructor(_ref = {}) { let { cliPkg, pluginDirs: morePluginDirs = [], docSite, done = () => {} } = _ref, args = _objectWithoutProperties(_ref, ["cliPkg", "pluginDirs", "docSite", "done"]); this.context = process.cwd(); this.pkg = null; this.cliPkg = cliPkg; this.pluginDirs = [path.join(__dirname, './plugins'), ...morePluginDirs]; debug.key('pluginDirs'); debug(this.pluginDirs); this.plugins = []; this.configFilepath = path.join(this.context, '.clirc.json'); this.askIfReConfig = false; this.options = {}; this.types = []; this.promptCompleteCbs = []; this.files = {}; this.docSite = docSite; this.done = done; this.args = args; } async create() { // ensure git await this.ensureGit(); await this.ensureSrcClean(); // read pkg await this.initPkg(); await this.loadOptions(); // resolve plugins await this.resolvePlugins(); // resolve plugin prompts first to let plugin have change to inject something that intro prompts need, but prompt them later this.pluginPrompts = this.resolvePluginPrompts(); this.introPrompts = this.resolveIntroPrompts(); await this.inquiryOptions(); // run generator log('🚀 Invoking generators...'); const generator = new Generator({ context: this.context, pkg: this.pkg, plugins: this.plugins, rootOptions: this.options, files: this.files, args: this.args }); await generator.generate(); // install stopSpinner(); log('⚙ Installing npm packages. This might take a while...'); await installDeps(_objectSpread({}, this.args)); log(); // git hint const hasGit = await this.isGitRepo(); if (!hasGit) { warn('you can use git to manage your source code'); log(); } else if (gitNotClean()) { log(' The following files have been updated / added / deleted:'); const { stdout } = await run('git ls-files --deleted --modified --others --directory --exclude-standard'); log(chalk.red(stdout.split(/\r?\n/g).map(line => ` ${line}`).join('\n'))); log(` You should review these changes with ${chalk.cyan('git diff')} and commit them.`); log(); } await this.done({ options: this.options, Types }); // log instructions stopSpinner(); success('🎉 Successfully init project'); info('👉 Get started with the following commands: '); info(' npm run dev'); if (this.docSite) { info(`文档: ${this.docSite}`); } log(); } isGitRepo() { return fs.existsSync(path.join(this.context, '.git')); } async ensureGit() { const hasGit = await this.isGitRepo(); if (!hasGit) { const { initGit } = await inquirer.prompt([{ name: 'initGit', type: 'list', message: '当前文件夹未检测到 git, 是否执行 git init 来初始化 git', choices: [{ name: '不要, 我就随便看看', value: false, short: '不要' }, { name: '要, 我需要版本控制', value: true, short: '要' }] }]); if (initGit) { await run.log('git init'); } } else { // ensure git clean await ensureGitClean(); } } async ensureSrcClean() { const isFirstTime = !fs.existsSync(this.configFilepath); if (fs.existsSync(path.join(this.context, './src'))) { const { removeSrc } = await inquirer.prompt([{ name: 'removeSrc', type: 'list', message: '如果你是第一次初始化, 建议删除 src 后再继续操作', default: !!isFirstTime, choices: [{ name: '不删', value: false }, { name: '删', value: true }] }]); if (removeSrc) { await removeFiles(this.context, ['src']); } } } async initPkg() { if (!this.pkg || !Object.keys(this.pkg)) { const pkgPath = path.join(this.context, 'package.json'); if (fs.pathExistsSync(pkgPath)) { // do not use readPkg, use the generated package.json as it is /* eslint-disable import/no-dynamic-require */ this.pkg = require(pkgPath); /* eslint-enable import/no-dynamic-require */ } else { this.pkg = { name: path.basename(this.context), version: '0.0.1', private: true, devDependencies: {} }; } } } async resolvePlugins() { const idToPlugin = (dir, id) => ({ id: id.replace(/^\.\//, 'built-in:'), dir, prepare: importFrom(path.join(dir, id), './prepare'), prompts: importFrom(path.join(dir, id), './prompts'), apply: importFrom(path.join(dir, id), './generator') }); /* eslint-disable no-restricted-syntax, no-await-in-loop */ for (const dir of this.pluginDirs) { const names = await fs.readdir(dir); const plugins = names.filter(name => name.charAt(0) !== '.').map(name => idToPlugin(dir, name)); this.plugins.push(...plugins); } /* eslint-enable no-restricted-syntax, no-await-in-loop */ debug.key('plugins'); debug(this.plugins); } async loadOptions() { const { configFilepath } = this; if (fs.existsSync(configFilepath)) { try { /* eslint-disable import/no-dynamic-require */ this.saveOptions(require(configFilepath)); /* eslint-enable import/no-dynamic-require */ debug(`load config from ${configFilepath}`); debug(JSON.stringify(this.options, null, 2)); log(); this.askIfReConfig = true; } catch (err) {// ignore err } } } async inquiryOptions() { const { configFilepath } = this; if (this.askIfReConfig) { const reConfig = await shouldReConfig(); if (!reConfig) { return; } } const options = await inquirer.prompt(this.resolveFinalPrompts()); // save options this.saveOptions(options); debug.key('options'); debug(this.options); this.files[configFilepath] = JSON.stringify(this.packOptionsToSave(), null, 2) + '\n'; // run cb registered by prompt modules /* eslint-disable no-restricted-syntax, no-await-in-loop */ for (const cb of this.promptCompleteCbs) { await cb(options); } /* eslint-enable no-restricted-syntax, no-await-in-loop */ } resolveFinalPrompts() { const prompts = [...this.introPrompts, ...this.pluginPrompts]; debug.key('prompts'); debug(prompts); return prompts; } resolveIntroPrompts() { // subtype has been removed, just for back-compatibility const defaultType = this.options.subtype || this.options.type; return [{ type: 'list', name: 'type', message: '请选择项目类型', default: defaultType, choices: this.types }]; } resolvePluginPrompts() { return this.plugins.reduce((prompts, plugin) => { let ps = plugin.prompts; if (isFunction(ps)) { const api = new PromptAPI(this); ps = ps({ api, Types, rootOptions: this.options, pkg: this.pkg }); } if (Array.isArray(ps)) { prompts.push(...ps); } return prompts; }, []); } saveOptions(options) { Object.assign(this.options, options); } packOptionsToSave() { const obj = cloneDeep(this.options); // reset version delete obj.version; obj.version = this.cliPkg.version; // remove deprecated options delete obj.vue; return obj; } } module.exports = Creator;