Node Cli
Node Cli
常用工具介绍
commander : 用于命令行参数解析
inquirer : 用于命令行交互
ora : 用于 loading 效果
figlet : 用于输出 ASCII 文艺字
picocolors : 用于输出样式
axios : 用于网络请求
simple-git : 用于下载远程仓库
cross-env : 用于兼容不同平台以及设置环境变量
仓库地址
新手教程
项目目录
1 | |-- node-cli |
配置项目
初始化项目
1
npm init -y
修改
package.json
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16{
"name": "create-yourname-app", // 项目名称 npm i create-yourname-app -g
"version": "1.0.0",
"description": "",
"author": "",
"license": "MIT",
"main": "index.js",
"files": ["index.js"], // 需要发布的文件
"bin": {
"create-yourname-app": "./index.js" // 全局命令 create-yourname-app (重要)
},
"keywords": [],
"scripts": {
"dev": "node ./index.js" // 监听文件变化
}
}修改
index.js
文件内容如下1
2
3
4// 标记为可执行文件
#! /usr/bin/env node
console.log("Hello, World!");
项目运行
本地运行
1
npm run dev
本地模仿全局命令
1
2
3
4
5
6# 在本地全局包中生成一个软连接指向当前目录
# 查看本地全局包 npm ls -g
npm link
# 执行 bin 命令
create-yourname-app上传
npm
1
2
3
4
5
6
7
8
9# 上传包
npm login
npm publish
# 全局安装
npm install create-yourname-app -g
# 执行 bin 命令
create-yourname-app
项目实践
项目目录
1 | |-- node-cli |
配置项目
初始化项目
1
npm init -y
修改
package.json
文件内容如下1
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{
"name": "@zxiaosi/cli", // 项目名称 npm i @zxiaosi/cli -g
"version": "1.0.0",
"description": "",
"author": "",
"license": "MIT",
"main": "index.js",
"files": ["src", "dist", "templates", "index.js"], // 需要发布的文件
"bin": {
"zxiaosi": "./index.js" // 全局命令 zxiaosi 重要
},
"keywords": [],
"publishConfig": {
"access": "public"
},
"dependencies": {},
"devDependencies": {
"typescript": "^5.7.3"
},
"scripts": {
"clean": "rimraf ./node_modules",
"dev": "tsc -w", // 监听文件变化
"build": "rimraf ./dist && tsc", // 打包
"preview": "node ./index.js", // 本地预览
"publish": "npm run build && npm publish"
}
}修改
index.js
文件内容如下1
2
3
4
5
6
7
8// 标记为可执行文件
#! /usr/bin/env node
// 引入 dist 中的入口文件
const CLI = require('./dist/index').default;
// 运行程序
new CLI().run();
配置 TypeScript
打包文件
安装
TypeScript
1
npm install --save-dev typescript
初始化
TypeScript
配置文件1
npx tsc --init
修改
tsconfig.json
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14{
"compilerOptions": {
"target": "ES5",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"declaration": true,
"outDir": "./dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
配置 figlet 展示ASCII 文艺字
(可选)
安装
figlet
1
2
3
4
5npm install figlet
npm i @types/figlet -D
// 控制台输出样式
npm i picocolors修改
src/core/registerFiglet.ts
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13import figlet from 'figlet';
import pc from 'picocolors';
import config from '../config';
/**
* ASCII 艺术字
*/
export default function () {
const figletText = figlet.textSync('zxiaosi', {
horizontalLayout: 'full',
});
console.log(pc.green(figletText));
}在
src/index.ts
中添加registerFiglet
方法1
2
3
4
5
6
7
8
9import { registerFiglet } from './core/registerFiglet';
export default class CLI {
constructor(appPath: any) {
registerFiglet();
}
run() {}
}效果如下
配置 axios 获取远程仓库分支
安装所需依赖
1
2
3
4
5# loading 效果, 高版本使用的 es 模块
npm i ora@5.4.1
# 获取远程 git 仓库信息
npm i axios修改
src/utils/handleGitBranches.ts
文件内容如下1
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
40import axios from 'axios';
import ora from 'ora';
const gethubApi =
'https://api.github.com/repos/zxiaosi/lerna-project/branches';
const giteeApi =
'https://gitee.com/api/v5/repos/zxiaosi/lerna-project/branches';
const spinner = ora('正在获取远程模板...');
/** 处理接口返回数据 */
const handleData = (origin: 'github' | 'gitee', data: any[]) => {
spinner.stop();
const branches = data
?.map((item: any) => ({ name: item.name, value: item.name }))
?.filter((item: any) => item.name !== 'master'); // 排除 master 分支
return { origin, branches };
};
/**
* 获取远程仓库的所有分支名
*/
export default async function () {
spinner.start();
try {
const resp = await axios.get(gethubApi);
return handleData('github', resp.data);
} catch (err) {
try {
const resp = await axios.get(giteeApi);
return handleData('gitee', resp.data);
} catch (err) {
spinner.fail('获取远程模板失败!');
throw new Error('Get remote template failed!');
}
}
}
配置 simple-git 下载远程仓库
安装所需依赖
1
2# 获取远程仓库
npm i simple-git修改
src/utils/handleDownloadZip.ts
文件内容如下1
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
37import ora from 'ora';
import { simpleGit, SimpleGit } from 'simple-git';
const git: SimpleGit = simpleGit({
progress({ method, stage, progress, processed, total }) {
console.log(
`git.${method} ${stage} stage ${progress}% complete, ${processed}/${total}`
);
},
});
/**
* 下载 zip 文件
* @param templateSource 模板源
* @param branch 分支
* @param outputPath 输出路径
*/
export default function (
templateSource: string,
branch: string,
outputPath: string
) {
const spinner = ora(`正在从 ${templateSource} 拉取远程模板...`).start();
return new Promise<void>(async (resolve, reject) => {
git
.clone(templateSource, outputPath, { '--branch': branch })
.then((resp) => {
spinner.succeed(`拉取远程模板成功!`);
resolve();
})
.catch((err) => {
spinner.fail(`拉取远程模板失败!`);
reject('exit');
});
});
}
配置 @inquirer/prompts 命令交互
安装所需依赖
1
2# 高版本不支持 node16
npm i @inquirer/prompts@3.3.2修改
src/utils/handleCheckFolder.ts
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24import fs from 'fs';
import ora from 'ora';
/** 检测文件夹是否为空 */
export default function (path: string) {
const spinner = ora('检测文件夹是否为空...').start();
return new Promise((resolve, reject) => {
fs.readdir(path, (err, files) => {
if (err) {
spinner.fail(`检测文件夹失败!`);
return reject('exit');
}
if (files.length !== 0) {
spinner.fail(`当前文件夹不为空!`);
return reject('exit');
}
spinner.stop();
return resolve(true);
});
});
}修改
src/core/registerPrompts.ts
文件内容如下1
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
59import path from 'path';
import { select } from '@inquirer/prompts';
import pc from 'picocolors';
import {
handleCheckFolder,
handleDownloadZip,
handleGitBranches,
} from '../utils';
const modeItems = [
{ name: '本地模板', value: 'local' },
{ name: '远程模板(Github)', value: 'github' },
{ name: '远程模板(Gitee)', value: 'gitee' },
];
const outputPath = path.join(
process.cwd(),
process.env.NODE_ENV === 'development' ? 'output' : ''
);
/**
* 命令交互逻辑
*/
export default async function () {
const mode = await select({
message: '请选择获取模板的方式',
choices: modeItems,
});
switch (mode) {
case 'local': {
console.log(pc.yellow('功能正在完善中...'));
break;
}
case 'github':
case 'gitee': {
const { origin, branches } = await handleGitBranches();
const template = await select({
message: '请选择要获取的模板',
choices: branches,
});
try {
const result = await handleCheckFolder(outputPath);
if (!result) return;
const templateSource = `https://${origin}.com/zxiaosi/lerna-project.git`;
await handleDownloadZip(templateSource, template, outputPath);
} catch (err: any) {
throw new Error(err);
}
break;
}
}
}修改
src/core/registerException.ts
文件内容如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/**
* 异常捕获
*/
export default function () {
process.on('uncaughtException', (error) => {
const { name, message } = error;
if (message.includes('User force closed the prompt')) {
console.log('\n👋 Until next time!\n');
} else {
if (error.message !== 'exit') {
console.log('\n🤖 Uncaught error!\n', error.message);
}
process.exit(1); // 退出程序
}
});
}
配置 cross-env 设置环境变量
安装所需依赖
1
npm i cross-env -D
修改
package.json
文件内容如下1
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{
"name": "@zxiaosi/cli",
"version": "1.0.6",
"description": "cli tool for qiankun",
"author": "zxiaosi",
"license": "MIT",
"main": "index.js",
"types": "/dist/index.d.ts",
"files": ["src", "dist", "templates", "index.js"],
"repository": {
"type": "git",
"url": "git+https://github.com/zxiaosi/node-cli.git"
},
"bin": {
"zxiaosi": "./index.js"
},
"keywords": ["qiankun"],
"publishConfig": {
"access": "public"
},
"dependencies": {
"@inquirer/prompts": "^3.3.2",
"axios": "^1.7.9",
"chokidar": "^4.0.3",
"figlet": "^1.8.0",
"ora": "^5.4.1",
"picocolors": "^1.1.1",
"simple-git": "^3.27.0"
},
"devDependencies": {
"@types/figlet": "^1.7.0",
"@types/node": "^22.13.2",
"cross-env": "^7.0.3",
"typescript": "^5.7.3"
},
"scripts": {
"clean": "rimraf ./node_modules",
"dev": "tsc -w",
"build": "rimraf ./dist && cross-env NODE_ENV=production tsc",
"preview": "cross-env NODE_ENV=development node ./index.js",
"preview:build": "cross-env NODE_ENV=production node ./index.js"
}
}
配置 src/index.ts
程序入口
1 | import { registerFiglet, registerException, registerPrompts } from './core'; |
运行项目
本地运行
1
2
3
4
5
6
7
8
9# 运行 npm run dev, 可以监听文件变化,并实时编译成 dist 目录下的文件
# 然后新开一个命令框运行 npm run preview 查看效果。
npm run dev
npm run preview
# 运行 npm run build, 编译成 dist 目录下的文件
# 然后运行 npm run preview:build 查看效果。
npm run build
npm run preview:build本地模仿全局命令
1
2
3
4
5
6# 在本地全局包中生成一个软连接指向当前目录
# 查看本地全局包 npm ls -g
npm link
# 执行 bin 命令
zxiaosi上传
npm
1
2
3
4
5
6
7
8# 上传包
npm publish
# 全局安装
npm install @zxiaosi/cli -g
# 执行 bin 命令
zxiaosi
npm 链接
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 小四先生的云!
评论