alvis-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +48 -0
- package/lib/create.js +115 -0
- package/lib/list.js +30 -0
- package/lib/title.js +28 -0
- package/package.json +33 -0
- package/utils/constant.js +47 -0
- package/utils/fs.js +47 -0
- package/utils/gitClone.js +19 -0
- package/utils/inquirer.js +22 -0
- package/utils/logSymbols.js +44 -0
package/index.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 1. 增加终端标题
|
|
5
|
+
* 2. 下载仓库
|
|
6
|
+
* 3. 美化下载仓库的过程
|
|
7
|
+
* 4. 解析命令行参数
|
|
8
|
+
* 4.1 alvis create <app-name> -t xxx -f -i
|
|
9
|
+
* 4.2 alvis list
|
|
10
|
+
* 5. 终端输出美化
|
|
11
|
+
*/
|
|
12
|
+
import { useTitle } from "./lib/title.js";
|
|
13
|
+
import { program } from "commander";
|
|
14
|
+
import chalk from "chalk";
|
|
15
|
+
import { listTable } from "./lib/list.js";
|
|
16
|
+
import { createProject } from "./lib/create.js";
|
|
17
|
+
import fs from "fs-extra";
|
|
18
|
+
|
|
19
|
+
const pkg = fs.readJsonSync(new URL("./package.json", import.meta.url));
|
|
20
|
+
|
|
21
|
+
program.version(pkg.version, "-v, --version", "alvis-cli版本");
|
|
22
|
+
|
|
23
|
+
program
|
|
24
|
+
.name("alvis")
|
|
25
|
+
.description("一个简单的脚手架工具")
|
|
26
|
+
.usage("<command> [options]")
|
|
27
|
+
.on("--help", () => {
|
|
28
|
+
useTitle("ALVIS");
|
|
29
|
+
console.log(
|
|
30
|
+
`\r\n Run ${chalk.cyan(`alvis <command> --help`)} for detailed usage of given command.`
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// 命令 alvis create
|
|
35
|
+
program
|
|
36
|
+
.command("create <app-name>")
|
|
37
|
+
.description("创建项目")
|
|
38
|
+
.usage("<app-name> [options]")
|
|
39
|
+
.option("-t, --template [template]", "使用模版创建项目")
|
|
40
|
+
.option("-f, --force", "强制覆盖本地同名项目")
|
|
41
|
+
.option("-i, --ignore", "忽略项目相关描述,快速创建项目")
|
|
42
|
+
.action(createProject);
|
|
43
|
+
|
|
44
|
+
// 命令 alvis list
|
|
45
|
+
program.command("list").description("列举所有可用的模版").action(listTable);
|
|
46
|
+
|
|
47
|
+
// 解析命令
|
|
48
|
+
program.parse(process.argv);
|
package/lib/create.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import logSymbols from "../utils/logSymbols.js";
|
|
3
|
+
import shell from "shelljs";
|
|
4
|
+
import { messages, templateArr } from "../utils/constant.js";
|
|
5
|
+
import {
|
|
6
|
+
inquirerChoose,
|
|
7
|
+
inquirerConfirm,
|
|
8
|
+
inquirerInput
|
|
9
|
+
} from "../utils/inquirer.js";
|
|
10
|
+
import { clone } from "../utils/gitClone.js";
|
|
11
|
+
import fs from "fs-extra";
|
|
12
|
+
import { removeDir, changePackageJson } from "../utils/fs.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Alvis create命令执行
|
|
16
|
+
* @param {*} appName
|
|
17
|
+
* @param {*} options
|
|
18
|
+
*/
|
|
19
|
+
export async function createProject(appName, options) {
|
|
20
|
+
// 1. 校验
|
|
21
|
+
// 1.1 项目名称是否是合法字符校验
|
|
22
|
+
if (appName.match(/[\u4E00-\u9FFF`~!@#$%&^*[\]()\\;:<.>/?]/g)) {
|
|
23
|
+
console.log(
|
|
24
|
+
logSymbols.error,
|
|
25
|
+
chalk.redBright("对不起,项目名称存在非法字符")
|
|
26
|
+
);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// 1.2 用户是否安装了git
|
|
30
|
+
if (!shell.which("git")) {
|
|
31
|
+
console.log(
|
|
32
|
+
logSymbols.error,
|
|
33
|
+
chalk.redBright("对不起,运行脚本必须安装git")
|
|
34
|
+
);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// 2. 模版选择(-t xxx)
|
|
38
|
+
let repository = "";
|
|
39
|
+
// 2.1 模版存在,则比对输入的名称是否存在对应的仓库模版
|
|
40
|
+
if (options.template) {
|
|
41
|
+
const templateName = options.template.trim();
|
|
42
|
+
const template = templateArr.find((item) => item.name === templateName);
|
|
43
|
+
if (!template) {
|
|
44
|
+
console.log(
|
|
45
|
+
logSymbols.error,
|
|
46
|
+
`不存在模版${chalk.yellowBright(templateName)}`
|
|
47
|
+
);
|
|
48
|
+
console.log(
|
|
49
|
+
`\r\n 运行${logSymbols.arrow} ${chalk.cyanBright("alvis list")} 查看所有可用模板\r\n`
|
|
50
|
+
);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
repository = template.value;
|
|
54
|
+
} else {
|
|
55
|
+
// 2.2 模版不存在,则问答式交互,让用户选择一个模版
|
|
56
|
+
const answer = await inquirerChoose(
|
|
57
|
+
chalk.blueBright("请选择一个项目模版:"),
|
|
58
|
+
templateArr
|
|
59
|
+
);
|
|
60
|
+
repository = answer;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 3. 是否覆盖已经存在的项目(-f)
|
|
64
|
+
// 3.1 如果不存在-f,则判断是否有同名项目
|
|
65
|
+
if (fs.existsSync(appName) && !options.force) {
|
|
66
|
+
console.log(
|
|
67
|
+
logSymbols.warning,
|
|
68
|
+
`已经存在项目文件夹${chalk.yellowBright(appName)}`
|
|
69
|
+
);
|
|
70
|
+
// 存在同名项目,则询问用户是否进行删除
|
|
71
|
+
const answer = await inquirerConfirm(
|
|
72
|
+
`是否删除文件夹${chalk.yellowBright(appName)}?`
|
|
73
|
+
);
|
|
74
|
+
// 3.1.1 删除
|
|
75
|
+
if (answer) {
|
|
76
|
+
removeDir(appName);
|
|
77
|
+
} else {
|
|
78
|
+
// 3.1.2 不删除则拉取项目失败
|
|
79
|
+
console.log(
|
|
80
|
+
logSymbols.error,
|
|
81
|
+
chalk.redBright(
|
|
82
|
+
`对不起,项目创建失败,存在同名文件夹,${chalk.yellowBright(appName)}`
|
|
83
|
+
)
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
} else if (fs.existsSync(appName) && options.force) {
|
|
87
|
+
// 3.2 如果存在-f
|
|
88
|
+
console.log(
|
|
89
|
+
logSymbols.warning,
|
|
90
|
+
`已经存在项目文件夹${chalk.yellowBright(appName)},强制删除`
|
|
91
|
+
);
|
|
92
|
+
// 3.2.1 强制删除文件夹以及下面的子文件
|
|
93
|
+
removeDir(appName);
|
|
94
|
+
}
|
|
95
|
+
// 4. 拉取项目
|
|
96
|
+
try {
|
|
97
|
+
await clone(repository, appName);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.log(logSymbols.error, chalk.redBright("对不起,项目拉取失败"));
|
|
100
|
+
}
|
|
101
|
+
// 5. -i是否存在
|
|
102
|
+
// 5.1 存在没有任何问题(不用进行任何操作)
|
|
103
|
+
// 5.2 不存在
|
|
104
|
+
if (!options.ignore) {
|
|
105
|
+
// 5.2.1 交互式,让用户手动输入需要更改的内容,对package.json文件进行修改
|
|
106
|
+
const answer = {};
|
|
107
|
+
for (const item of messages) {
|
|
108
|
+
const res = await inquirerInput(item.message, item.validate);
|
|
109
|
+
answer[item.name] = res;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 写入json文件
|
|
113
|
+
await changePackageJson(appName, answer);
|
|
114
|
+
}
|
|
115
|
+
}
|
package/lib/list.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { templateArr } from "../utils/constant.js";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { table } from "table";
|
|
4
|
+
import logSymbols from "../utils/logSymbols.js";
|
|
5
|
+
/**
|
|
6
|
+
* list 命令
|
|
7
|
+
* 表格的格式对list模版进行输出
|
|
8
|
+
*/
|
|
9
|
+
export function listTable() {
|
|
10
|
+
const data = templateArr.map((item) => [
|
|
11
|
+
chalk.greenBright(item.name),
|
|
12
|
+
chalk.blueBright(item.value),
|
|
13
|
+
chalk.yellowBright(item.desc)
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
data.unshift([
|
|
17
|
+
chalk.greenBright("模版名称"),
|
|
18
|
+
chalk.blueBright("模版仓库"),
|
|
19
|
+
chalk.yellowBright("模版描述")
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
const config = {
|
|
23
|
+
header: {
|
|
24
|
+
alignment: "center",
|
|
25
|
+
content: chalk.yellowBright(logSymbols.star, "所有可用的模板")
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
console.log(table(data, config));
|
|
30
|
+
}
|
package/lib/title.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import figlet from "figlet";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
|
|
4
|
+
function gradient(text) {
|
|
5
|
+
const gradientColors = [
|
|
6
|
+
"#FF6B6B",
|
|
7
|
+
"#FF8E6B",
|
|
8
|
+
"#FFB36B",
|
|
9
|
+
"#FFD86B",
|
|
10
|
+
"#EFFF6B",
|
|
11
|
+
"#B8FF6B"
|
|
12
|
+
];
|
|
13
|
+
return text
|
|
14
|
+
.split("")
|
|
15
|
+
.map((char, i) => {
|
|
16
|
+
const color =
|
|
17
|
+
gradientColors[
|
|
18
|
+
Math.floor((i / text.length) * (gradientColors.length - 1))
|
|
19
|
+
];
|
|
20
|
+
return chalk.hex(color)(char);
|
|
21
|
+
})
|
|
22
|
+
.join("");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function useTitle(message) {
|
|
26
|
+
const text = figlet.textSync(message);
|
|
27
|
+
console.log(gradient(text));
|
|
28
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "alvis-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"alvis": "./index.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [],
|
|
14
|
+
"author": "",
|
|
15
|
+
"license": "ISC",
|
|
16
|
+
"packageManager": "pnpm@10.28.0",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@inquirer/prompts": "^8.2.0",
|
|
19
|
+
"chalk": "^5.6.2",
|
|
20
|
+
"commander": "^14.0.2",
|
|
21
|
+
"download-git-repo": "^3.0.2",
|
|
22
|
+
"figlet": "^1.9.4",
|
|
23
|
+
"fs-extra": "^11.3.3",
|
|
24
|
+
"ora": "^9.0.0",
|
|
25
|
+
"shelljs": "^0.10.0",
|
|
26
|
+
"table": "^6.9.0"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"lib/",
|
|
30
|
+
"utils/",
|
|
31
|
+
"index.js"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export const templateArr = [
|
|
2
|
+
{
|
|
3
|
+
name: "webpack-template",
|
|
4
|
+
value: "yingside/webpack-template",
|
|
5
|
+
desc: "基于webpack5自定义初始化vue3项目模板"
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
name: "vue-cli-template",
|
|
9
|
+
value: "yingside/vue-cli-template",
|
|
10
|
+
desc: "基于vue-cli自定义vue3项目模板"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: "vite-template",
|
|
14
|
+
value: "yingside/vite-template",
|
|
15
|
+
desc: "基于vite的vue3 + 前端工具链项目模板"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: "vue-admin-box",
|
|
19
|
+
value: "cmdparkour/vue-admin-box",
|
|
20
|
+
desc: "cmdparkour vue3 + vite中后台项目模板"
|
|
21
|
+
}
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export const messages = [
|
|
25
|
+
{
|
|
26
|
+
message: "请输入项目名称:",
|
|
27
|
+
name: "name",
|
|
28
|
+
validate(val) {
|
|
29
|
+
if (val.match(/[\u4E00-\u9FFF`~!@#$%&^*[\]()\\;:<.>/?]/g)) {
|
|
30
|
+
return "项目名称存在非法字符";
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
message: "请输入项目关键词(,分割):",
|
|
37
|
+
name: "keywords"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
message: "请输入项目描述:",
|
|
41
|
+
name: "description"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
message: "请输入作者名称:",
|
|
45
|
+
name: "author"
|
|
46
|
+
}
|
|
47
|
+
];
|
package/utils/fs.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import path from "path";
|
|
5
|
+
|
|
6
|
+
const pwd = process.cwd();
|
|
7
|
+
|
|
8
|
+
const resolveApp = (relativePath) => path.resolve(pwd, relativePath);
|
|
9
|
+
|
|
10
|
+
export function removeDir(dir) {
|
|
11
|
+
const spinner = ora("正在删除文件夹....").start();
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
fs.rmSync(resolveApp(dir), {
|
|
15
|
+
recursive: true
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
spinner.succeed(chalk.greenBright(`删除文件夹${chalk.cyan(dir)}成功`));
|
|
19
|
+
} catch (err) {
|
|
20
|
+
spinner.fail(chalk.redBright(`删除文件夹${chalk.cyan(dir)}失败`));
|
|
21
|
+
console.log(err);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function changePackageJson(name, info) {
|
|
26
|
+
try {
|
|
27
|
+
const pkg = await fs.readJson(resolveApp(`${name}/package.json`));
|
|
28
|
+
|
|
29
|
+
Object.keys(info).forEach((item) => {
|
|
30
|
+
if (item === "name") {
|
|
31
|
+
pkg[item] = info[item] && info[item].trim() ? info[item].trim() : name;
|
|
32
|
+
} else if (item === "keywords" && info[item] && info[item].trim()) {
|
|
33
|
+
pkg[item] = info[item].split(",");
|
|
34
|
+
} else if (info[item] && info[item].trim()) {
|
|
35
|
+
pkg[item] = info[item];
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
await fs.writeJson(resolveApp(`${name}/package.json`), pkg, { spaces: 2 });
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.log(
|
|
42
|
+
logSymbols.error,
|
|
43
|
+
chalk.red("对不起,修改自定义package.json失败,请手动修改")
|
|
44
|
+
);
|
|
45
|
+
console.log(err);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import download from "download-git-repo";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
|
|
5
|
+
export function clone(remote, name, options = false) {
|
|
6
|
+
const spinner = ora("正在拉取项目....").start();
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
download(remote, name, options, (err) => {
|
|
9
|
+
if (err) {
|
|
10
|
+
spinner.fail(chalk.red(err));
|
|
11
|
+
reject(err);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
spinner.succeed(chalk.green("项目拉取成功"));
|
|
16
|
+
resolve();
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { select, confirm, input } from "@inquirer/prompts";
|
|
2
|
+
|
|
3
|
+
export async function inquirerChoose(message, choices) {
|
|
4
|
+
return await select({
|
|
5
|
+
message,
|
|
6
|
+
choices
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function inquirerConfirm(message) {
|
|
11
|
+
return await confirm({
|
|
12
|
+
message,
|
|
13
|
+
default: "y"
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function inquirerInput(message, validate) {
|
|
18
|
+
return await input({
|
|
19
|
+
message,
|
|
20
|
+
validate
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 检查终端是否支持unicode
|
|
5
|
+
* @returns
|
|
6
|
+
*/
|
|
7
|
+
export function isUnicodeSupported() {
|
|
8
|
+
if (process.platform !== "win32") {
|
|
9
|
+
return process.env.TERM !== "linux"; // Linux console (kernel)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
Boolean(process.env.WT_SESSION) || // 是否是windows终端
|
|
14
|
+
Boolean(process.env.TERMINUS_SUBLIME) || // 是否Terminus (<0.2.27)
|
|
15
|
+
process.env.ConEmuTask === "{cmd::Cmder}" || // ConEmu and cmder
|
|
16
|
+
process.env.TERM_PROGRAM === "Terminus-Sublime" ||
|
|
17
|
+
process.env.TERM_PROGRAM === "vscode" ||
|
|
18
|
+
process.env.TERM === "xterm-256color" ||
|
|
19
|
+
process.env.TERM === "alacritty" ||
|
|
20
|
+
process.env.TERMINAL_EMULATOR === "JetBrains-JediTerm"
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const main = {
|
|
25
|
+
info: chalk.blue("ℹ"),
|
|
26
|
+
success: chalk.green("✔"),
|
|
27
|
+
warning: chalk.yellow("⚠"),
|
|
28
|
+
error: chalk.red("✖"),
|
|
29
|
+
arrow: chalk.yellow("➦"),
|
|
30
|
+
star: chalk.cyan("✵")
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const fallback = {
|
|
34
|
+
info: chalk.blue("i"),
|
|
35
|
+
success: chalk.green("√"),
|
|
36
|
+
warning: chalk.yellow("‼"),
|
|
37
|
+
error: chalk.red("×"),
|
|
38
|
+
arrow: chalk.yellow("->"),
|
|
39
|
+
star: chalk.cyan("*")
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const logSymbols = isUnicodeSupported() ? main : fallback;
|
|
43
|
+
|
|
44
|
+
export default logSymbols;
|