脚手架安装项目模板架构设计
mongodb中数据修改,增加一个字段type
,为normal
或custom
;
判断是否为自定义模板
js
async installTemplate() {
if (this.templateInfo) {
if (!this.templateInfo.type) {
// 赋默认值
this.templateInfo.type = TEMPLATE_TYPE_NORMAL;
}
if (this.templateInfo.type === TEMPLATE_TYPE_NORMAL) {
// 标准安装
await this.installNormalTemplate();
} else if (this.templateInfo.type === TEMPLATE_TYPE_CUSTOM) {
// 自定义安装
await this.installCustomTemplate();
} else {
throw new Error("项目模版类型无法识别!");
}
} else {
throw new Error("项目模版信息不存在!");
}
}
拷贝项目模板功能
js
// 标准安装
async installNormalTemplate() {
let spinner = spinnerStart("正在安装模版");
try {
//拷贝模版代码至当前目录
const templatePath = path.resolve(
this.templateNpm.cacheFilepath,
"template"
);
const targetPath = process.cwd();
// 确保目录存在,不存在会创建一个
fse.ensureDirSync(templatePath);
fse.ensureDirSync(targetPath);
fse.copySync(templatePath, targetPath);
await sleep();
} catch (e) {
throw e;
} finally {
spinner.stop(true);
log.success("模版安装成功");
}
}
项目模板安装依赖和启动命令
步骤如下: 1.依赖安装 2.启动命令执行
修改一下mongodb的数据;
js
db.project.update({'name':'vue3标准模版'},{$set:{'installCommand':'npm install', 'startCommand':'npm run serve'}})
commands/init/lib/index.js
async installTemplate() {
if (this.templateInfo) {
if (!this.templateInfo.type) {
// 赋默认值
this.templateInfo.type = TEMPLATE_TYPE_NORMAL;
}
if (this.templateInfo.type === TEMPLATE_TYPE_NORMAL) {
// 标准安装
await this.installNormalTemplate();
} else if (this.templateInfo.type === TEMPLATE_TYPE_CUSTOM) {
// 自定义安装
await this.installCustomTemplate();
} else {
throw new Error("项目模版类型无法识别!");
}
} else {
throw new Error("项目模版信息不存在!");
}
}
// 标准安装
async installNormalTemplate() {
let spinner = spinnerStart("正在安装模版");
try {
//拷贝模版代码至当前目录
const templatePath = path.resolve(
this.templateNpm.cacheFilepath,
"template"
);
const targetPath = process.cwd();
// 确保目录存在,不存在会创建一个
fse.ensureDirSync(templatePath);
fse.ensureDirSync(targetPath);
fse.copySync(templatePath, targetPath);
await sleep();
} catch (e) {
throw e;
} finally {
spinner.stop(true);
// log.success("模版安装成功");
}
// 依赖安装
const { installCommand, startCommand } = this.templateInfo;
let installRes;
if (installCommand && installCommand.length > 0) {
const cmdArr = installCommand.split(" ");
const cmd = cmdArr[0];
const args = cmdArr.slice(1);
installRes = await execAsync(cmd, args, {
stdio: "inherit",
cwd: process.cwd(),
});
}
if (installRes !== 0) {
throw new Error("依赖安装失败");
}
// 启动命令执行
let startRes;
if (startCommand && startCommand.length > 0) {
const cmdArr = startCommand.split(" ");
const cmd = cmdArr[0];
const args = cmdArr.slice(1);
startRes = await execAsync(cmd, args, {
stdio: "inherit",
cwd: process.cwd(),
});
}
}
白名单命令检测功能
防止mongodb中isntallCommand被人篡改;所以在执行命令之前增加白名单检测;
js
// 白名单命令
const WHITE_COMMAND = ["npm", "cnpm"];
// 白名单命令检测
checkCommand(cmd) {
if (WHITE_COMMAND.includes(cmd) > 0) {
return cmd;
}
return null;
}
async execCommand(commands, errorMsg) {
let commandsRes;
if (commands && commands.length > 0) {
const cmdArr = commands.split(" ");
const cmd = this.checkCommand(cmdArr[0]);
const args = cmdArr.slice(1);
if (cmd) {
commandsRes = await execAsync(cmd, args, {
stdio: "inherit",
cwd: process.cwd(),
});
} else {
throw new Error("白名单命令检测失败");
}
}
if (installRes !== 0) {
throw new Error(errorMsg);
}
return commandsRes;
}
async installNormalTemplate() {
await this.execCommand(installCommand, "依赖安装失败");
await this.execCommand(startCommand, "项目启动失败");
}
ejs相关开发
项目名称自动格式化
在mj-cli-dev-template/mj-cli-dev-template-vue3/templage/package.json
中修改:
package.json
{
"name": "<%= className %>",
"version": "<%= version %>",
js
lerna add kebab-case@1.0.0 commands/init
packageName格式化:AbcEfg => abc-efg
commands/init/lib/index.js
getProjectInfo(){
// 略
if (projectInfo.projectName) {
// AbcEfg => abc-efg
projectInfo.className = require("kebab-case")(
projectInfo.projectName
).replace(/^-/, "");
}
// 添加两个变量,ejs模板使用
if (projectInfo.projectVersion) {
projectInfo.version = projectInfo.projectVersion;
}
}
commands/init/lib/index.js
async installNormalTemplate() {
const opts = {
ignore: ["node_modules/**", "public/**"],
};
await this.ejsRender(opts);
}
// ejs模板关键方法
async ejsRender(options) {
const dir = process.cwd();
return new Promise((resolve, reject) => {
require("glob")(
"**",
{
cwd: dir,
ignore: options.ignore,
nodir: true,
},
(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,
this.projectInfo,
{},
(err, result) => {
if (err) {
reject1(err);
} else {
// ejs模板转换不成功,渲染结果需重新写入
fse.writeFileSync(filePath, result);
resolve1(result);
}
}
);
});
})
)
.then(() => {
resolve();
})
.catch((err) => {
reject(err);
});
}
);
});
}
安装成功后,本地的目录pacakgeName和version就是我们手动输入的值;
跑一下整个流程是可以成功启动项目的!
init命令直接传入项目名称功能支持
如果输入的命令中带有projectName,就不显示【请输入版本号】的命令;
getProjectInfo方法改造;
commands/init/lib/index.js
// 获取基本信息
async getProjectInfo() {
function isValidName(v) {
return /^[a-zA-Z]+([-][a-zA-Z][a-zA-Z0-9]*|[_][a-zA-Z][a-zA-Z0-9]*|[a-zA-Z0-9])*$/.test(
v
);
}
// 1.选择创建项目或组件
let type = "";
let projectInfo = {};
let isProjectNameValid = false;
if (this.projectName != "" && isValidName(this.projectName)) {
isProjectNameValid = true;
projectInfo.projectName = this.projectName;
}
type = (
await inquirer.prompt([
{
type: "chioce",
message: "请选择初始化类型",
default: TYPE_PROJECT,
name: "type",
chioces: [
{
name: "项目",
value: TYPE_PROJECT,
},
{
name: "组件",
value: TYPE_COMPONENT,
},
],
},
])
).type;
if (type === TYPE_PROJECT) {
// 2.获取项目的基本信息
let projectPrompt = [
{
type: "input",
name: "projectVersion",
message: "请输入版本号",
default: "1.0.0",
validate: function (v) {
const done = this.async();
setTimeout(function () {
if (!!!semver.valid(v)) {
done("请输入合法的版本号");
} else {
done(null, true);
}
}, 0);
},
filter: function (v) {
if (!!semver.valid(v)) {
return semver.valid(v);
} else {
return v;
}
},
},
{
type: "list",
name: "projectTemplate",
message: "请选择项目模板",
choices: this.createTemplateChioice(),
},
];
const projectNamePrompt = {
type: "input",
name: "projectName",
message: "请输入项目名称",
default: "",
validate: function (v) {
const done = this.async();
// Do async stuff
setTimeout(function () {
// 1.输入的首字符;
// 2.尾字符必须是英文字符或数字,不能为字符
// 3.字符近允许是"-_""
if (!isValidName(v)) {
done("请输入合法的项目名称");
} else {
done(null, true);
}
}, 0);
},
filter: function (v) {
return v;
},
};
if (!isProjectNameValid) {
projectPrompt.unshift(projectNamePrompt);
}
const obj = await inquirer.prompt(projectPrompt);
projectInfo = {
...projectInfo,
type,
...obj,
};
if (projectInfo.projectName) {
// AbcEfg => abc-efg
projectInfo.className = require("kebab-case")(
projectInfo.projectName
).replace(/^-/, "");
}
if (projectInfo.projectVersion) {
projectInfo.version = projectInfo.projectVersion;
}
} else if (type === TYPE_COMPONENT) {
}
// return 项目的基本信息object
return projectInfo;
}
项目初始化的流程就结束了。