xgb-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/README.md +56 -0
- package/bin/index.js +29 -0
- package/package.json +29 -0
- package/src/commands/init.js +117 -0
- package/src/templates.js +23 -0
- package/src/utils/download.js +33 -0
- package/src/utils/install.js +40 -0
- package/src/utils/logger.js +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# xgb-cli
|
|
2
|
+
|
|
3
|
+
A CLI tool to download and initialize projects from GitHub templates.
|
|
4
|
+
|
|
5
|
+
## templates
|
|
6
|
+
|
|
7
|
+
- [react-components-menorepo-template](https://github.com/xgbbing/react-components-monorepo-template)
|
|
8
|
+
基于 dumi + @ice/pkg 的 React Menorepo 组件库项目模板
|
|
9
|
+
- [react-components-repo-template](https://github.com/xgbbing/react-components-repo-template)
|
|
10
|
+
基于 dumi 的 React 组件库项目模板
|
|
11
|
+
- [vue3-admin-repo-template](https://github.com/xgbbing/vue3-admin-repo-template)
|
|
12
|
+
基于 Vue3 + TypeScript + Vite + Pinia + Element-Plus 的 后台管理系统项目模板
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -g xgb-cli
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Or link it locally for development:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm link
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
### Initialize a new project
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
xgb-cli init
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This will prompt you for:
|
|
35
|
+
- Project name
|
|
36
|
+
- GitHub template
|
|
37
|
+
|
|
38
|
+
### Use with options
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Examples
|
|
42
|
+
xgb-cli init my-app
|
|
43
|
+
xgb-cli init -t react-components-menorepo-template
|
|
44
|
+
xgb-cli init my-app -t react-components-menorepo-template
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Features
|
|
48
|
+
|
|
49
|
+
- 🚀 Quick project initialization
|
|
50
|
+
- 📥 Download templates from GitHub
|
|
51
|
+
- 💬 Interactive prompts
|
|
52
|
+
- ✨ Clean and simple interface
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { program } = require('commander');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const figlet = require('figlet');
|
|
6
|
+
const { version } = require('../package.json');
|
|
7
|
+
const { initProject } = require('../src/commands/init');
|
|
8
|
+
|
|
9
|
+
console.log(
|
|
10
|
+
chalk.cyan(
|
|
11
|
+
figlet.textSync('XGB CLI', { horizontalLayout: 'full' })
|
|
12
|
+
)
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.name('xgb-cli')
|
|
17
|
+
.description('前端脚手架CLI工具')
|
|
18
|
+
.version(version, '-v, --version', '查看版本号');
|
|
19
|
+
|
|
20
|
+
// Init command
|
|
21
|
+
program
|
|
22
|
+
.command('init [project-name]')
|
|
23
|
+
.description('初始化一个新项目')
|
|
24
|
+
.option('-t, --template <template>', '指定模板')
|
|
25
|
+
.action((projectName, options) => {
|
|
26
|
+
initProject(projectName, options);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
program.parse(process.argv);
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "xgb-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "前端脚手架CLI",
|
|
5
|
+
"bin": {
|
|
6
|
+
"xgb-cli": "./bin/index.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"start": "node bin/index.js"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"cli",
|
|
13
|
+
"github",
|
|
14
|
+
"template",
|
|
15
|
+
"init"
|
|
16
|
+
],
|
|
17
|
+
"author": "xgb.xu@qq.com",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"commander": "^11.1.0",
|
|
21
|
+
"inquirer": "^8.2.5",
|
|
22
|
+
"ora": "^5.4.1",
|
|
23
|
+
"chalk": "^4.1.2",
|
|
24
|
+
"download-git-repo": "^2.0.0",
|
|
25
|
+
"figlet": "^1.7.0",
|
|
26
|
+
"fs-extra": "^11.0.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {}
|
|
29
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const inquirer = require('inquirer');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const templates = require('../templates');
|
|
6
|
+
const { downloadTemplate } = require('../utils/download');
|
|
7
|
+
const logger = require('../utils/logger');
|
|
8
|
+
|
|
9
|
+
const ora = require('ora');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Initialize a new project from GitHub template
|
|
13
|
+
* @param {Object} options - Command options
|
|
14
|
+
* @param {string} options.projectName - projectName
|
|
15
|
+
* @param {string} options.template - GitHub template (format: owner/repo)
|
|
16
|
+
*/
|
|
17
|
+
async function initProject(projectName, options) {
|
|
18
|
+
try {
|
|
19
|
+
const answers = await promptUser(projectName, options)
|
|
20
|
+
const { name, template } = answers;
|
|
21
|
+
|
|
22
|
+
const targetDir = path.join(process.cwd(), name);
|
|
23
|
+
|
|
24
|
+
if (fs.existsSync(targetDir)) {
|
|
25
|
+
const { overwrite } = await inquirer.prompt([
|
|
26
|
+
{
|
|
27
|
+
type: 'confirm',
|
|
28
|
+
name: 'overwrite',
|
|
29
|
+
message: chalk.yellow(`目录${name}已经存在,是否覆盖?`),
|
|
30
|
+
default: false
|
|
31
|
+
}
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
if (!overwrite) {
|
|
35
|
+
logger.warn('已取消创建')
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
await fs.remove(targetDir);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const selectTemplate = templates.find(t => t.value === template)
|
|
43
|
+
|
|
44
|
+
await downloadTemplate(selectTemplate.repo, targetDir);
|
|
45
|
+
|
|
46
|
+
await updatePackageJson(targetDir, name);
|
|
47
|
+
|
|
48
|
+
printSuccess(name);
|
|
49
|
+
|
|
50
|
+
} catch (err) {
|
|
51
|
+
logger.error(`创建项目失败: ${err.message}`)
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function promptUser(projectName, options) {
|
|
57
|
+
const questions = []
|
|
58
|
+
|
|
59
|
+
if (!projectName) {
|
|
60
|
+
questions.push({
|
|
61
|
+
type: 'input',
|
|
62
|
+
name: 'name',
|
|
63
|
+
message: '请输入项目名称:',
|
|
64
|
+
default: 'my-app',
|
|
65
|
+
validate(input) {
|
|
66
|
+
if (!input.trim()) return '项目名称不能为空';
|
|
67
|
+
if (!/^[a-zA-Z0-9-_]+$/.test(input)) return '项目名称只能包含字母、数字、下划线、中划线';
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!options.template) {
|
|
74
|
+
questions.push({
|
|
75
|
+
type: 'list',
|
|
76
|
+
name: 'template',
|
|
77
|
+
message: '请选择项目模板:',
|
|
78
|
+
choices: templates.map(t => ({
|
|
79
|
+
name: `${t.name} - ${chalk.gray(t.description)}`,
|
|
80
|
+
value: t.value
|
|
81
|
+
})),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const answers = await inquirer.prompt(questions);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
name: projectName || answers.name,
|
|
89
|
+
template: options.template || answers.template
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function updatePackageJson(targetDir, projectName) {
|
|
94
|
+
const pkgPath = path.join(targetDir, 'package.json');
|
|
95
|
+
if (fs.existsSync(pkgPath)) {
|
|
96
|
+
const pkg = await fs.readJson(pkgPath);
|
|
97
|
+
pkg.name = projectName;
|
|
98
|
+
pkg.version = '0.1.0';
|
|
99
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function printSuccess(name) {
|
|
104
|
+
console.log()
|
|
105
|
+
logger.success('✅ 项目初始化成功!')
|
|
106
|
+
console.log()
|
|
107
|
+
logger.info('👉 执行以下命令开始开发:')
|
|
108
|
+
console.log()
|
|
109
|
+
logger.warn(` cd ${name}`)
|
|
110
|
+
logger.warn(' pnpm install')
|
|
111
|
+
logger.warn(' pnpm start')
|
|
112
|
+
console.log()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = {
|
|
116
|
+
initProject
|
|
117
|
+
};
|
package/src/templates.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module.exports = [
|
|
2
|
+
{
|
|
3
|
+
name: 'react-components-menorepo-template',
|
|
4
|
+
description: '基于 dumi + @ice/pkg 的 React Menorepo 组件库项目模板',
|
|
5
|
+
value: 'react-components-menorepo-template',
|
|
6
|
+
repo: 'github:xgbbing/react-components-menorepo-template',
|
|
7
|
+
branch: 'main'
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
name: 'react-components-repo-template',
|
|
11
|
+
description: '基于 dumi 的 React 组件库项目模板',
|
|
12
|
+
value: 'react-components-repo-template',
|
|
13
|
+
repo: 'github:xgbbing/react-components-repo-template',
|
|
14
|
+
branch: 'main'
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'vue3-admin-repo-template',
|
|
18
|
+
description: '基于 Vue3 + TypeScript + Vite + Pinia + Element-Plus 的 后台管理系统项目模板',
|
|
19
|
+
value: 'vue3-admin-repo-template',
|
|
20
|
+
repo: 'github:xgbbing/vue3-admin-repo-template',
|
|
21
|
+
branch: 'main'
|
|
22
|
+
}
|
|
23
|
+
]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const downloadGitRepo = require('download-git-repo');
|
|
2
|
+
const ora = require('ora');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Download template
|
|
7
|
+
* @param {string} repo - GitHub repo (github:user/repo#branch)
|
|
8
|
+
* @param {string} targetDir - Target directory
|
|
9
|
+
*/
|
|
10
|
+
async function downloadTemplate(repo, targetDir) {
|
|
11
|
+
const spinner = ora({
|
|
12
|
+
text: `正在下载模板 ${chalk.cyan(repo)}...`,
|
|
13
|
+
color: 'cyan'
|
|
14
|
+
}).start();
|
|
15
|
+
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
downloadGitRepo(repo, targetDir,
|
|
18
|
+
{
|
|
19
|
+
clone: false
|
|
20
|
+
}, // false 用 download,true 用 git clone (私有仓库用 true)
|
|
21
|
+
(err) => {
|
|
22
|
+
if (err) {
|
|
23
|
+
spinner.fail('模板下载失败');
|
|
24
|
+
reject(err);
|
|
25
|
+
} else {
|
|
26
|
+
spinner.succeed('模板下载成功');
|
|
27
|
+
resolve();
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = { downloadTemplate };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const ora = require('ora');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
function installDeps(targetDir, packageManager = 'pnpm') {
|
|
6
|
+
const spinner = ora({
|
|
7
|
+
text: `正在使用 ${chalk.cyan(packageManager)} 安装依赖...`,
|
|
8
|
+
color: 'cyan'
|
|
9
|
+
}).start();
|
|
10
|
+
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
// windows 兼容
|
|
13
|
+
const isWin = process.platform === 'win32';
|
|
14
|
+
const cmd = isWin ? `${packageManager}.cmd` : packageManager;
|
|
15
|
+
|
|
16
|
+
const child = spawn(cmd, ['install'], {
|
|
17
|
+
cwd: targetDir, // 在项目目录下执行
|
|
18
|
+
stdio: 'inherit' // 继承父进程的输出
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
child.on('close', (code) => {
|
|
22
|
+
if (code !== 0) {
|
|
23
|
+
spinner.fail(chalk.red('依赖安装失败'));
|
|
24
|
+
reject(new Error(`${packageManager} install 失败,退出码: ${code}`));
|
|
25
|
+
} else {
|
|
26
|
+
spinner.succeed(chalk.green('依赖安装成功'));
|
|
27
|
+
resolve();
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
child.on('error', (err) => {
|
|
32
|
+
spinner.fail(chalk.red('依赖安装出错'));
|
|
33
|
+
reject(err);
|
|
34
|
+
});
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
installDeps
|
|
40
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
info: (...args) => {
|
|
5
|
+
console.log(chalk.cyan(...args));
|
|
6
|
+
},
|
|
7
|
+
success: (...args) => {
|
|
8
|
+
console.log(chalk.green(...args));
|
|
9
|
+
},
|
|
10
|
+
warn: (...args) => {
|
|
11
|
+
console.log(chalk.yellow(...args));
|
|
12
|
+
},
|
|
13
|
+
error: (...args) => {
|
|
14
|
+
console.log(chalk.red(...args));
|
|
15
|
+
},
|
|
16
|
+
};
|