@ky-cli/create
Version: 
ky-cli 创建项目包
292 lines (279 loc) • 7.47 kB
JavaScript
;
const fs = require("fs");
const path = require("path");
const inquirer = require("inquirer");
const fsExtra = require("fs-extra");
const semver = require("semver");
const userHome = require("user-home");
const glob = require("glob");
const ejs = require("ejs");
const validNpmName = require("validate-npm-package-name");
const Command = require("@ky-cli/command");
const log = require("@ky-cli/log");
const Mongdb = require("@ky-cli/handle-mongdb");
const {
	DEFAULT_CLI_HOME,
	MONGDBURL,
	MONGDBNAME
} = require("@ky-cli/global-config");
const {
	spinnerStart,
	sleep,
	execAsync,
	toLowerCase
} = require("@ky-cli/utils");
const Package = require("@ky-cli/package");
const SetSubProject = require("./setSubProject");
const mongdb = new Mongdb(MONGDBURL, MONGDBNAME);
class InitCommand extends Command {
	// 1.保存项目名 + 保存force状态
	init() {
		this.projectName = this._argv[0];
		this.force = !!this._cmd.force;
		log.debug("projectName", this.projectName);
		log.debug("force", this.force);
	}
	// 2.执行init命令
	async exec() {
		try {
			// 1.准备阶段
			this.projectInfo = await this.prepare();
			if (this.projectInfo) {
				// 2.下载模版
				await this.downloadTemplate();
				// 3.安装模版
				await this.installTemplate();
			}
		} catch (e) {
			log.error(e.message);
			if (process.env.LOG_LEVEL === "debug") {
				console.error(e);
			}
		}
	}
	// 2-1.准备阶段返回项目信息
	async prepare() {
		this.templateConfig = await mongdb.query("project_info");
		mongdb.close();
		return this.getProjectInfo();
	}
	async getProjectInfo() {
		let projectInfo = await inquirer.prompt([
			{
				type: "list",
				name: "template",
				message: "请选择您需要创建的项目模版",
				default: "@ky-template/common-single",
				choices: this.templateConfig.map(e => ({
					name: e.name,
					value: e.npmName
				}))
			},
			{
				type: "input",
				name: "projectName",
				message: "请输入项目名称",
				default: this.projectName || "ky-cli-project",
				validate: v => validNpmName(v).validForNewPackages
			},
			{
				type: "number",
				name: "projectPort",
				message: "请输入当前子项目端口号",
				default: 7001,
				validate: v => {
					const ssp = new SetSubProject();
					return !ssp
						.getChildMess()
						.map(e => Number(e.port))
						.includes(v);
				},
				when: answer => answer.template === "@ky-template/microservice-child"
			}
		]);
		projectInfo.packageName = toLowerCase(projectInfo.projectName);
		projectInfo.projectVersion = "1.0.0";
		return { ...projectInfo };
	}
	async downloadTemplate() {
		this.templateInfo = this.templateConfig.find(
			e => e.npmName === this.projectInfo.template
		);
		if (this.templateInfo) {
			const targetPath = path.resolve(userHome, DEFAULT_CLI_HOME, "template");
			const storeDir = path.resolve(targetPath, "node_modules");
			const templateNpm = new Package({
				targetPath,
				storeDir,
				name: this.templateInfo.npmName,
				version: this.templateInfo.version
			});
			// 模版文件存在那么更新对应模版,否则进行模版安装
			if (await templateNpm.exists()) {
				const spinner = spinnerStart("正在进行模版更新安装");
				console.log("");
				try {
					await sleep();
					await templateNpm.update();
					this.templateNpm = templateNpm;
				} catch (e) {
					throw e;
				} finally {
					spinner.stop(true);
					if (await templateNpm.exists()) {
						log.success("模版更新成功!");
					}
				}
			} else {
				const spinner = spinnerStart("正在进行模版下载安装");
				console.log("");
				try {
					await sleep();
					await templateNpm.install();
					this.templateNpm = templateNpm;
					log.success("模版下载成功!");
				} catch (e) {
					throw e;
				} finally {
					spinner.stop(true);
					if (await templateNpm.exists()) {
						log.success("模版下载成功!");
					}
				}
			}
		}
	}
	async installTemplate() {
		const targetPath = this.getTargetPath();
		if (!this.isCwdEmpty(targetPath)) {
			const isEmptyDir = await this.emptyDir(targetPath);
			if (!isEmptyDir) return false;
		}
		const spinner = spinnerStart("正在进行代码本地复制......");
		try {
			// 1.判断当前执行目录是否为空且判断当前
			const templatePath = path.resolve(
				this.templateNpm.getCacheFilePath(this.templateInfo.version),
				"template"
			);
			await fsExtra.ensureDirSync(templatePath);
			await fsExtra.copySync(templatePath, targetPath);
		} catch (e) {
			throw new Error(e.message);
		} finally {
			spinner.stop();
		}
		const ignore = ["**/node_modules/**", ...(this.templateInfo.ignore || [])];
		await this.ejsRender({ ignore });
		const { installCommand, startCommand } = this.templateInfo;
		if (this.projectInfo.template === "@ky-template/microservice-child") {
			const setSubProject = new SetSubProject();
			setSubProject.setRouteContent();
			setSubProject.setAppsContent();
		}
		installCommand &&
			(await this.execCommand(installCommand, "项目npm包初始化完成!"));
		// await this.execCommand("npm run eslint", "项目格式化完成");
		startCommand && (await this.execCommand(startCommand, "项目启动成功!"));
	}
	getTargetPath() {
		let targetPath = process.cwd();
		switch (this.projectInfo.template) {
			case "@ky-template/microservice-child":
				targetPath = path.resolve(
					process.cwd(),
					`packages/${this.projectInfo.projectName}`
				);
				break;
		}
		return targetPath;
	}
	async emptyDir(localPath) {
		let force = false;
		if (!this.force) {
			force = (
				await inquirer.prompt([
					{
						type: "confirm",
						name: "force",
						message: "该目录下存在文件或者目录,是否进行强制覆盖",
						default: false
					}
				])
			).force;
			if (!force) {
				return false;
			}
		}
		if (force || this.force) {
			const spinner = spinnerStart("本地文件清空中......");
			// 清空当前目录
			fsExtra.emptyDirSync(localPath);
			spinner.stop(true);
		}
		return true;
	}
	async ejsRender(options) {
		const dir = process.cwd();
		const projectInfo = this.projectInfo;
		return new Promise((resolve, reject) => {
			glob(
				"**",
				{
					cwd: dir,
					ignore: options.ignore || "",
					nodir: true
				},
				function (err, files) {
					if (err) {
						reject(err);
					}
					Promise.all(
						files.map(file => {
							const filePath = path.join(dir, file);
							return new Promise((resolve1, reject1) => {
								ejs.renderFile(filePath, projectInfo, {}, (err, result) => {
									if (err) {
										reject1(err);
									} else {
										fsExtra.writeFileSync(filePath, result);
										resolve1(result);
									}
								});
							});
						})
					)
						.then(() => {
							resolve();
						})
						.catch(err => {
							reject(err);
						});
				}
			);
		});
	}
	async execCommand(commands, msg) {
		const [command, ...args] = commands.split(" ");
		const ret = await execAsync(command, args, {
			cwd: process.cwd(),
			stdio: "inherit"
		});
		if (ret !== 0) {
			throw new Error(msg + "失败!");
		}
		log.success(msg);
	}
	isCwdEmpty(localPath) {
		fsExtra.ensureDirSync(localPath);
		const fileList = fs
			.readdirSync(localPath)
			.filter(file => !file.startsWith(".") && !file.includes("node_modules"));
		return fileList.length === 0;
	}
}
function init(argv) {
	return new InitCommand(argv);
}
module.exports = init;
module.exports.InitCommand = InitCommand;