脚手架cli开发
# 简介
只要提到脚手架你就会想到,vue-cli 、 create-react-app 、 dva-cli ... 他们的特点不用多说那就是专一! 但是在公司中你会发现有以下一系列的问题!
- 业务类型多
- 多次造轮子,项目升级等问题
- 公司代码规范, 无法统一, 多人协作更为方便,不需要把文件传来传去。
很多时候我们开发时需要新建项目,把已有的项目代码复制一遍,保留基础能力。(但是这个过程非常琐碎而又耗时)。那我们可以自己定制化模板,自己实现一个属于自己的脚手架。来解决这些问题
# 开发流程
# 搭建集成了三大 UI 库的脚手架工具
先幻想下要实现的功能:
根据模板初始化项目 sy-cli create project-name
初始化配置文件 sy-cli config set repo repo-name
# 必备第三方模块
- commander.js (opens new window),可以自动的解析命令和参数,用于处理用户输入的命令。
- Inquirer.js (opens new window),通用的命令行用户界面集合,用于和用户进行交互。
- download-git-repo (opens new window),下载并提取 git 仓库,用于下载项目模板。
- ora (opens new window),下载过程久的话,可以用于显示下载中的动画效果。
- metalsmith (opens new window) :读取所有文件,实现模板渲染。
- consolidate (opens new window) :统一模板引擎。
- handlebars.js (opens new window),模板引擎,将用户提交的信息动态填充到文件中。
- chalk (opens new window),可以给终端的字体加上颜色。
- log-symbols (opens new window),可以在终端上显示出 √ 或 × 等的图标。
# 流程总括
- 创建脚手架执行文件
- 使用commander.js解析命令行指令参数
- 使用 inquirer.js 设计命令行交互
- 创建工程化模板并push到GitHub
- 使用 download-git-repo 下载模板
- 使用ora 和 chalk 美化命令行
- 使用ora增加loading效果
- 使用chalk改变命令行颜色
# 图示流程
# 部分相关代码
const inquirer = require('inquirer')
async function chooseTemplate(){
const promptList = [
{
type: "list", // type决定交互的方式,比如当值为input的时候就是输入的形式,list就是单选,checkbox是多选...
name: "template",
message: "选择一个需要创建的工程化模板",
choices: [
{
name: "vue (js版本的vue全家桶工程化模板)",
value: "vue-template-default",
},
{
name: "mock-server (用于模拟接口数据的本地node服务模板)",
value: "mock-server",
}
],
},
];
const answers = await inquirer.prompt(promptList); // 执行命令行交互,并将交互的结果返回
const {template} = answers
console.log(`你选择的模板是:${template}`)
return template // 返回我们选择的模板
}
module.exports = {
chooseTemplate
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
console.log(chalk.rgb(69, 39, 160)('你选择的模板是 👉'),chalk.bgRgb(69, 39, 160)(template))
// 下载前提示loading
const spinner = ora({
text: '正在下载模板...',
color: "yellow",
spinner: {
interval: 80,
frames: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
},
});
spinner.start();
const chalk = require('chalk')
const symbols = require('log-symbols')
console.log(symbols.success, chalk.green('SUCCESS'))
console.log(symbols.error, chalk.red('FAIL'))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 开发实践
# 工程创建
npm init -y # 初始化package.json
npm install eslint husky --save-dev # eslint是负责代码校验工作,husky提供了git钩子功能
npx eslint --init # 初始化eslint配置文件
2
3
# 1.创建文件夹
├── bin
│ └── www // 全局命令执行的根文件
├── package.json
├── src
│ ├── main.js // 入口文件
│ └── utils // 存放工具方法
│── .huskyrc // git hook
│── .eslintrc.json // 代码规范校验
2
3
4
5
6
7
8
# 2.eslint配置
配置package.json 校验src文件夹下的代码
"scripts": {
"lint":"eslint src"
}
2
3
# 3.配置husky
当使用git提交前校验代码是否符合规范
{
"hooks": {
"pre-commit": "npm run lint"
}
}
2
3
4
5
# 4.链接全局包
设置在命令下执行sy-cli时调用bin目录下的www文件
"bin": {
"sy-cli": "./bin/www"
}
2
3
可以设置多入口名称
"bin": {
"sy": "./bin/www",
"sy-cli": "./bin/www"
}
2
3
4
www文件中使用main作为入口文件,并且以node环境执行此文件
#! /usr/bin/env node
require('../src/main.js');
2
链接包到全局下使用
npm link
可以成功的在命令行中使用sy-cli
命令,并且可以执行main.js文件!
# 解析命令行参数
commander:The complete solution for node.js command-line interfaces
先吹一波commander,commander可以自动生成help,解析选项参数!
像这样 vue-cli --help!
像这样 vue-cli create <project-namne>
# 1.使用commander
npm install commander
main.js就是我们的入口文件
const program = require('commander');
program.version('0.0.1')
.parse(process.argv); // process.argv就是用户在命令行中传入的参数
2
3
执行sy-cli --help
是不是已经有一提示了!
这个版本号应该使用的是当前cli项目的版本号,我们需要动态获取,并且为了方便我们将常量全部放到util下的constants
文件夹中
const { name, version } = require('../../package.json');
module.exports = {
name,
version,
};
2
3
4
5
6
这样我们就可以动态获取版本号了
const program = require('commander');
const { version } = require('./utils/constants');
program.version(version)
.parse(process.argv);
2
3
4
5
6
# 2.配置指令命令
根据我们想要实现的功能配置执行动作,遍历产生对应的命令; 可简写设置;
const actionsMap = {
create: { // 创建模板
description: 'create project',
alias: 'cr',
examples: [
'sy-cli create <template-name>',
],
},
config: { // 配置配置文件
description: 'config info',
alias: 'c',
examples: [
'sy-cli config get <k>',
'sy-cli config set <k> <v>',
],
},
'*': {
description: 'command not found',
},
};
// 循环创建命令
Object.keys(actionsMap).forEach((action) => {
program
.command(action) // 命令的名称
.alias(actionsMap[action].alias) // 命令的别名
.description(actionsMap[action].description) // 命令的描述
.action(() => { // 动作
console.log(action);
});
});
program.version(version)
.parse(process.argv);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 3.编写help命令
监听help命令打印帮助信息
program.on('--help', () => {
console.log('Examples');
Object.keys(actionsMap).forEach((action) => {
(actionsMap[action].examples || []).forEach((example) => {
console.log(` ${example}`);
});
});
});
2
3
4
5
6
7
8
# 4.找到输出的参数【要点】
// {force:true}
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("config [value]")
.alias('cf')
.description("inspect and modify the config")
.option("-g, --get <path>", "get value from option")
.option("-s, --set <path> <value>")
.option("-d, --delete <path>", "delete option from config")
.action((value, cmd) => {
console.log(value, cleanArgs(cmd)); // 调用config模块去实现
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# create命令
create命令的主要作用就是去git仓库中拉取模板并下载对应的版本到本地,如果有模板则根据用户填写的信息渲染好模板,生成到当前运行命令的目录下~
action(() => { // 动作
if (action === '*') { // 如果动作没匹配到说明输入有误
console.log(acitonMap[action].description);
} else { // 引用对应的动作文件 将参数传入
require(path.resolve(__dirname, action))(...process.argv.slice(3));
}
}
2
3
4
5
6
7
根据不同的动作,动态引入对应模块的文件
创建create.js
// 创建项目
module.exports = async (projectName) => {
console.log(projectName);
};
2
3
4
执行sy-cli create project
,可以打印出 project
# 1.拉取项目
我们需要获取仓库中的所有模板信息,我的模板全部放在了git上,这里就以git为例,我通过axios去获取相关的信息~~~
npm i axios
这里借助下github的 api (opens new window)
const axios = require('axios');
// 1).获取仓库列表
const fetchRepoList = async () => {
// 获取当前组织中的所有仓库信息,这个仓库中存放的都是项目模板
const { data } = await axios.get('https://api.github.com/orgs/sy-cli/repos');
return data;
};
module.exports = async (projectName) => {
let repos = await fetchRepoList();
repos = repos.map((item) => item.name);
console.log(repos)
};
2
3
4
5
6
7
8
9
10
11
12
13
发现在安装的时候体验很不好没有任何提示,而且最终的结果我希望是可以供用户选择的!
# 2.inquirer & ora
来解决上面提到的问题
npm i inquirer ora
module.exports = async (projectName) => {
const spinner = ora('fetching repo list');
spinner.start(); // 开始loading
let repos = await fetchRepoList();
spinner.succeed(); // 结束loading
//proce.fail()// 下载失败调用
//proce.succeed()// 下载成功调用
// 选择模板
repos = repos.map((item) => item.name);
const { repo } = await Inquirer.prompt({
name: 'repo',
type: 'list',
message: 'please choice repo template to create project',
choices: repos, // 选择模式
});
console.log(repo);
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
看到的命令行中选择的功能基本都是基于inquirer实现的,可以实现不同的询问方式;
其他提示的优化:
if (err) {
lqProcess.fail()
console.log(symbols.error, chalk.red(err))
} else {
lqProcess.succeed()
console.log(symbols.success, chalk.green('创建成功'))
}
2
3
4
5
6
7
# 3.获取版本信息
和获取模板一样,我们可以故技重施
const fetchTagList = async (repo) => {
const { data } = await axios.get(`https://api.github.com/repos/sy-cli/${repo}/tags`);
return data;
};
// 获取版本信息
spinner = ora('fetching repo tags');
spinner.start();
let tags = await fetchTagList(repo);
spinner.succeed(); // 结束loading
// 选择版本
tags = tags.map((item) => item.name);
const { tag } = await Inquirer.prompt({
name: 'tag',
type: 'list',
message: 'please choice repo template to create project',
choices: tags,
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
发现每次都需要去开启loading、关闭loading,重复的代码!我们来简单的封装下;
封装请求库处理方法:
const wrapFetchAddLoding = (fn, message) => async (...args) => {
const spinner = ora(message);
spinner.start(); // 开始loading
const r = await fn(...args);
spinner.succeed(); // 结束loading
return r;
};
// 这回用起来舒心多了~~~
let repos = await wrapFetchAddLoding(fetchRepoList, 'fetching repo list')();
let tags = await wrapFetchAddLoding(fetchTagList, 'fetching tag list')(repo);
2
3
4
5
6
7
8
9
10
# 4.下载项目
我们已经成功获取到了项目模板名称和对应的版本,那我们就可以直接下载啦!
npm i download-git-repo #其实也可以直接用git clone实现下载功能
很遗憾的是这个方法不是promise方法,没关系我们自己包装一下
const { promisify } = require('util');
const downLoadGit = require('download-git-repo');
downLoadGit = promisify(downLoadGit);
2
3
node中已经帮你提供了方法,将异步的api可以快速转化成promise的形式~
下载前先找个临时目录来存放下载的文件,来~继续配置常量;下载目录处理;
const downloadDirectory = `${process.env[process.platform === 'darwin' ? 'HOME' : 'USERPROFILE']}/.template`;
这里我们将文件下载到当前用户下的.template
文件中,由于系统的不同目录获取方式不一样,process.platform
在windows下获取的是 win32
我这里是mac 所有获取的值是 darwin
,在根据对应的环境变量获取到用户目录
const download = async (repo, tag) => {
let api = `sy-cli/${repo}`; // 下载项目
if (tag) {
api += `#${tag}`;
}
const dest = `${downloadDirectory}/${repo}`; // 将模板下载到对应的目录中
await downLoadGit(api, dest);
return dest; // 返回下载目录
};
// 下载项目
const target = await wrapFetchAddLoding(download, 'download template')(repo, tag);
2
3
4
5
6
7
8
9
10
11
12
ncp拷贝/可直接fs-extra
对于简单的项目可以直接把下载好的项目拷贝到当前执行命令的目录下即可。
安装ncp
可以实现文件的拷贝功能
npm i ncp
像这样:
let ncp = require('ncp');
ncp = promisify(ncp);
// 将下载的文件拷贝到当前执行命令的目录下
await ncp(target, path.join(path.resolve(), projectName));
2
3
4
当然这里可以做的更严谨一些,判断一下当前目录下是否有重名文件等..., 还有很多细节也需要考虑像多次创建项目是否要利用已经下载好的模板,大家可以自由的发挥~
# 5.模板编译
刚才说的是简单文件,那当然直接拷贝就好了,但是有的时候用户可以定制下载模板中的内容,拿package.json
文件为例,用户可以根据提示给项目命名、设置描述等
在项目模板中增加ask.js
module.exports = [
{
type: 'confirm',
name: 'private',
message: 'ths resgistery is private?',
},
...
]
2
3
4
5
6
7
8
根据对应的询问生成最终的package.json
下载的模板中使用了ejs
模板
{
"name": "vue-template",
"version": "0.1.2",
"private": "<%=private%>",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"vue": "^2.6.10"
},
"autor":"<%=author%>",
"description": "<%=description%>",
"devDependencies": {
"@vue/cli-service": "^3.11.0",
"vue-template-compiler": "^2.6.10"
},
"license": "<%=license%>"
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
写到这里,大家应该想到了!核心原理就是将下载的模板文件,依次遍历根据用户填写的信息渲染模板,将渲染好的结果拷贝到执行命令的目录下
安装需要用到的模块【要点】
npm i metalsmith ejs consolidate
const MetalSmith = require('metalsmith'); // 遍历文件夹
let { render } = require('consolidate').ejs;
render = promisify(render); // 包装渲染方法
// 没有ask文件说明不需要编译
if (!fs.existsSync(path.join(target, 'ask.js'))) {
await ncp(target, path.join(path.resolve(), projectName));
} else {
await new Promise((resovle, reject) => {
MetalSmith(__dirname)
.source(target) // 遍历下载的目录
.destination(path.join(path.resolve(), projectName)) // 输出渲染后的结果
.use(async (files, metal, done) => {
// 弹框询问用户
const result = await Inquirer.prompt(require(path.join(target, 'ask.js')));
const data = metal.metadata();
Object.assign(data, result); // 将询问的结果放到metadata中保证在下一个中间件中可以获取到
delete files['ask.js'];
done();
})
.use((files, metal, done) => {
Reflect.ownKeys(files).forEach(async (file) => {
let content = files[file].contents.toString(); // 获取文件中的内容
if (file.includes('.js') || file.includes('.json')) { // 如果是js或者json才有可能是模板
if (content.includes('<%')) { // 文件中用<% 我才需要编译
content = await render(content, metal.metadata()); // 用数据渲染模板
files[file].contents = Buffer.from(content); // 渲染好的结果替换即可
}
}
});
done();
})
.build((err) => { // 执行中间件
if (!err) {
resovle();
} else {
reject();
}
});
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
这里的逻辑就是上面描述的那样,实现了模板替换!到此安装项目的功能就完成了,我们发现这里面所有用到的地址的路径都写死了,我们希望这是一个更通用的脚手架,可以让用户自己配置拉取的地址~
# config命令
新建config.js 主要的作用其实就是配置文件的读写操作,当然如果配置文件不存在需要提供默认的值,先来编写常量
constants.js
的配置
const configFile = `${process.env[process.platform === 'darwin' ? 'HOME' : 'USERPROFILE']}/.zhurc`; // 配置文件的存储位置
const defaultConfig = {
repo: 'sy-cli', // 默认拉取的仓库名
};
2
3
4
编写config.js
const fs = require('fs');
const { defaultConfig, configFile } = require('./util/constants');
module.exports = (action, k, v) => {
if (action === 'get') {
console.log('获取');
} else if (action === 'set') {
console.log('设置');
}
// ...
};
2
3
4
5
6
7
8
9
10
一般rc
类型的配置文件都是ini
格式也就是:
repo=sy-cli
register=github
2
下载 ini
模块解析配置文件
npm i ini
这里的代码很简单,无非就是文件操作了
const fs = require('fs');
const { encode, decode } = require('ini');
const { defaultConfig, configFile } = require('./util/constants');
const fs = require('fs');
const { encode, decode } = require('ini');
const { defaultConfig, configFile } = require('./util/constants');
module.exports = (action, k, v) => {
const flag = fs.existsSync(configFile);
const obj = {};
if (flag) { // 配置文件存在
const content = fs.readFileSync(configFile, 'utf8');
const c = decode(content); // 将文件解析成对象
Object.assign(obj, c);
}
if (action === 'get') {
console.log(obj[k] || defaultConfig[k]);
} else if (action === 'set') {
obj[k] = v;
fs.writeFileSync(configFile, encode(obj)); // 将内容转化ini格式写入到字符串中
console.log(`${k}=${v}`);
} else if (action === 'getVal') {
return obj[k];
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
getVal
这个方法是为了在执行create命令时可以获取到配置变量
const config = require('./config');
const repoUrl = config('getVal', 'repo');
2
这样我们可以将create方法中所有的sy-cli
全部用获取到的值替换掉啦!
到此基本核心的方法已经ok!剩下的大家可以自行扩展啦!
# 关于exts
远程仓库增加exts接口目录,其中放置 ask.js, hook.js, 方便扩展,若没有则使用内置的ask
ask.js
可以是数组, 可以是一个返回数组的方法;遵循 Inquirer prompt参数 (opens new window)
hook.js
目前只提供生成成功后方法也就是after,提供给开发者作扩展用。after函数,可以是async function, 返回Promise实例的函数,普通函数。
ask.js
/*
* @Author: samy
* @email: yessz#foxmail.com
* @time: 2020-07-05 19:41:12
* @modAuthor: samy
* @modTime: 2020-12-22 00:34:19
* @desc: ask替换文件 ejs文件替换
* @Copyright © 2015~2020 BDP FE
*/
const { execSync } = require("child_process")
module.exports = function askCreator(template = "") {
let user = execSync("git config --global user.name", { encoding: "utf-8" })
let email = execSync("git config --global user.email", { encoding: "utf-8" })
user = user.trim()
email = email.trim()
return [
{
type: "input",
name: "name",
message: "package name(en)",
default: template,
validate(input) {
const done = this.async()
if (input.trim().length === 0) {
done("project name is empty")
return
}
done(null, true)
},
},
{
type: "confirm",
name: "private",
message: "Is the project private ?",
},
{
type: "input",
name: "description",
message: "description",
},
{
type: "input",
name: "author",
message: "author",
default: email,
},
{
type: "list",
name: "license",
message: "license",
choices: [
"MIT",
"BSD 2-clause 'Simplified'",
"Apache 2.0",
"GNU General Public v3.0",
"BSD 3-clause",
"Eclipse Public 1.0",
"GNU Affero General Public v3.0",
"GNU General Public v2.0",
"GNU Lesser General Public v2.1",
"GNU Lesser General Public v3.0",
"Mozilla Public 2.0",
"The Unlicense",
],
},
{
type: "input",
name: "proName",
message: "sub sys pro name (en)",
validate(input) {
const done = this.async()
if (input.trim().length === 0) {
done("sub pro name is empty")
return
}
done(null, true)
},
},
// {
// type: "input",
// name: "git",
// message: "user/repo",
// default: `${user}/${template}`,
// validate(input) {
// const done = this.async();
// if (!/\w+\/\w+/.test(input)) {
// done("Please input like user/repo");
// return;
// }
// done(null, true);
// },
// },
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
askblk.js
/*
* @Author: samy
* @email: yessz#foxmail.com
* @time: 2020-07-05 19:41:12
* @modAuthor: samy
* @modTime: 2020-12-22 00:37:37
* @desc: blk 模式下 ask替换文件 ejs文件替换
* @Copyright © 2015~2020 BDP FE
*/
module.exports = function askCreator(isMod, template = "") {
const header = [
{
type: "input",
name: "proName",
message: "current pro name(en)",
validate(input) {
const done = this.async();
if (input.trim().length === 0) {
done("pro name is empty");
return;
}
done(null, true);
},
},
];
const mod = [
{
type: "input",
name: "moduleName",
message: "当前模块名(en)",
default: isMod ? template : "",
validate(input) {
const done = this.async();
if (input.trim().length === 0) {
done("module name is empty");
return;
}
done(null, true);
},
},
];
const page = [
{
type: "input",
name: "pageName",
message: "pageName(en)",
default: !isMod ? template : "",
validate(input) {
const done = this.async();
if (input.trim().length === 0) {
done("page name name path is empty");
return;
}
done(null, true);
},
},
];
const msg = isMod ? "Module" : "Page";
const footer = [
{
type: "input",
name: "name",
message: `${msg} name title(cn)`,
default: `${msg} case`,
},
];
if (isMod) {
return [...header, ...mod, ...footer];
} else {js
return [...header, ...mod, ...page, ...footer];
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# 简单示例
#!/usr/bin/env node
const chalk = require('chalk')
console.log('Hello, cli!')
console.log(chalk.green('init创建'))
const fs = require('fs')
const program = require('commander')
const download = require('download-git-repo')
const inquirer = require('inquirer')
const ora = require('ora')
const symbols = require('log-symbols')
const handlebars = require('handlebars')
program
.version(require('./package').version, '-v, --version')
.command('init <name>')
.action(name => {
console.log(name)
inquirer
.prompt([
{
type: 'input',
name: 'author',
message: '请输入你的名字'
}
])
.then(answers => {
console.log(answers.author)
const lqProcess = ora('正在创建...')
lqProcess.start()
download(
'direct:https://github.com/bdp-tpl/xxx.git',
name,
{ clone: true },
err => {
if (err) {
lqProcess.fail()
console.log(symbols.error, chalk.red(err))
} else {
lqProcess.succeed()
const fileName = `${name}/package.json`
const meta = {
name,
author: answers.author
}
if (fs.existsSync(fileName)) {
const content = fs.readFileSync(fileName).toString()
const result = handlebars.compile(content)(meta)
fs.writeFileSync(fileName, result)
}
console.log(symbols.success, chalk.green('创建成功'))
}
}
)
})
})
program.parse(process.argv)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 项目发布
# npm
先确保是npm源,再登陆发布处理;
nrm use npm
npm publish # 已经发布成功~~
2
推荐发布到github中,方便和项目统一管理;
"scripts": {
"c": "babel src -d lib",
"w": "npm run c -- -watch",
"start": "npm run w",
"b": "npm run c && npm publish",
"bc": "npm --force unpublish rat-cli"
},
2
3
4
5
6
7
可以通过npm install sy-cli -g
进行安装啦!
# github
# 1.修改 package.json
- name为 @用户名/原包名
- 把仓库上传到github,并把地址贴到
repository
里,仓库的名字和包名是一样的 - registry为 https://npm.pkg.github.com/用户名
"name": "@samyzh/rat-cli",
"repository":"https://github.com/samyzh/rat-cli",
"publishConfig": {
"registry": "https://npm.pkg.github.com/samyzh"
},
2
3
4
5
会让你输入密码,这个密码是githubtoken,注意,是githubtoken ,是githubtoken。重要的事说3遍,因为这个设计有点反人类,它是粘贴并且看不见粘贴上没有。githubtoken在github的setting里面生成。
# 2.登录npm
npm login --registry=https://npm.pkg.github.com --scope=@samyzh
username 为你的github用户名
password 为刚才第一步生成的token# PKG_TOKEN
2
3
# 3. 发包
npm run build
npm publish
2
# 4.在github上搜索包名
# 项目版本号管理
package.json
中的version
字段代表的是该项目的版本号。每当项目发布新版本时,需要将version
字段进行相应的更新以便后期维护。虽然可以手动的修改vsersion
字段,但是为了整个发布过程的自动化,尽量使用 npm version (opens new window) 指令来自动更新version
:
npm version (v)1.2.3 # 显示设置版本号为 1.2.3
npm version major # 大版本号加 1,其余版本号归 0
npm version minor # 小版本号加 1,修订号归 0
npm version patch # 修订号加 1
2
3
4
显示的设置版本号时,版本号必须符合
semver
规范,允许在版本号前加上个v
标识。
如果不想让此次更新正式发布,还可以创建预发布版本:
# 当前版本号为 1.2.3
npm version prepatch# 版本号变为 1.2.4-0,也就是 1.2.4 版本的第一个预发布版本
npm version preminor# 版本号变为 1.3.0-0,也就是 1.3.0 版本的第一个预发布版本
npm version premajor# 版本号变为 2.0.0-0,也就是 2.0.0 版本的第一个预发布版本
npm version prerelease# 版本号变为 2.0.0-1,也就是使预发布版本号加一
2
3
4
5
在git
环境中,执行npm version
修改完版本号以后,还会默认执行git add
->git commit
->git tag
操作:
其中commit message
默认是自动修改完的版本号,可以通过添加-m/--message
选项来自定义commit message
:
npm version xxx -m "upgrade to %s for reasons" # %s 会自动替换为新版本号
比如执行npm version minor -m "feat(version): upgrade to %s for reasons"
后:
如果git
工作区还有未提交的修改,npm version
将会执行失败,可以加上-f/--force
后缀来强制执行。
如果不想让npm version
指令影响你的git
仓库,可以在指令中使用`--no-git-tag-v
# 相关问题
# 渲染特殊文件
# 解析碰到的问题;Or, if you meant to create an async function, pass
async: true
as an option.
修复render.js
if (content.includes(['<%=', '<%-'])) {
const extList = [
".js",
".json",
".jsx",
".ts",
".tsx",
".css",
".less",
".sass",
];
export default function () {
return function _render(files, metal, done) {
const meta = metal.metadata();
Object.keys(files).forEach(async (file) => {
const ext = path.extname(file);
if (extList.includes(ext)) {
let content = files[file].contents.toString();
if (content.includes(['<%=', '<%-'])) {
content = await render(content, meta);
files[file].contents = Buffer.from(content);
}
}
});
done();
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
download-git-repo 下载出错 status 128
return new Promise((resolve, reject) => {
const spinner = ora(`正在下载模板`)
spinner.start();
console.log(downLoadURL)
download(downLoadURL, target, {
clone: true
}, (err) => {
if (err) {
let errStr = err.toString()
spinner.fail();
reject(err);
if (errStr.includes("status 128")) {
console.log('\n', logSymbols.warning, chalk.yellow("Git默认开启了SSL验证,执行下面命令关闭后再重试即可;"))
console.log(logSymbols.info, chalk.green("git config --global http.sslVerify false"))
}
} else {
spinner.succeed();
resolve(target);
}
})
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
最后还是有data-set.min.js
做相关的设置,目前在document.ejs
中先移除,不做extend处理;直接用npm引入;
发现还是不行,得还原代码;做.min.js不做替换处理;通过ignore处理; 排除处理;
metalsmith
.metadata(answers)
.source("./")
//.ignore('public')
const ext = path.extname(file);
const basename = path.basename(file)
const lastSplits = basename.split('.').slice(-2)
const hasIncludeMin = lastSplits.includes('min') // 移除.min.js包好
if (extList.includes(ext) && !hasIncludeMin) {
2
3
4
5
6
7
8
9
10
# 处理md头部
<% content="---" %><%= content %>
pageComponent:
name: Catalogue
data:
key: 01.体系1
imgUrl: /img/web.png
description: 体系1相关文档
title: 体系1
date: 2020-03-11 21:50:55
permalink: /sys1/
sidebar: false
article: false
comment: false
editLink: false
<%= content %>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 内置相关字段
内置 author, email, day, time,用于处理特殊替换
function getDefaultEjs() {
let { author, email } = getGItInfo();
let day = getNowFormatDay();
let time = getNowFormatTime();
return { author, email, day, time };
}
let loader, hook, reply;
const defaultEjs = getDefaultEjs();
reply = await inquirer.prompt(ask);
loader = loading("👉生成中...", dir);
const ejsInfo = Object.assign(defaultEjs, reply);
await metal(resolve(proPath), resolve(root, dir), ejsInfo);
await remove(`${resolve(dir, exts.dir)}`);
loader.succeed(`👏生成完成 ${dir}`);
try {
hook = betterRequire(`${proPath}/${exts.hook}`);
} catch (e) {
hook = { after() {} };
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 相关链接
https://docs.gitlab.com/ee/api/
https://docs.github.com/en/free-pro-team@latest/rest
https://samyzh.github.io/rat-cli/