lzx-test-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/.vscode/extensions.json +5 -0
- package/bin/index.js +274 -0
- package/package.json +34 -0
- package/template-main/package.zip +0 -0
- package/template-sub/bdpTest2.zip +0 -0
- package/tsconfig.json +46 -0
package/bin/index.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import inquirer from 'inquirer';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
// import ora from 'ora';
|
|
8
|
+
import compressing from 'compressing'; //支持- tar - gzip - tgz - zip
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { spawn } from 'child_process';
|
|
11
|
+
import fetch from 'node-fetch';
|
|
12
|
+
import cliProgress from 'cli-progress';
|
|
13
|
+
// import download from 'download-git-repo';
|
|
14
|
+
console.log(
|
|
15
|
+
chalk.green('开始创建项目!')
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
*
|
|
20
|
+
* @description: 根据不同的压缩格式进行解压
|
|
21
|
+
* @returns
|
|
22
|
+
*/
|
|
23
|
+
function unCompressByType(filePath, unzipTarget, fileExtname) {
|
|
24
|
+
switch (fileExtname) {
|
|
25
|
+
case '.zip':
|
|
26
|
+
return compressing.zip.uncompress(filePath, unzipTarget);
|
|
27
|
+
case '.tgz':
|
|
28
|
+
return compressing.tgz.uncompress(filePath, unzipTarget);
|
|
29
|
+
case '.tar':
|
|
30
|
+
return compressing.tar.uncompress(filePath, unzipTarget);
|
|
31
|
+
case '.gz':
|
|
32
|
+
return compressing.gzip.uncompress(filePath, unzipTarget);
|
|
33
|
+
default:
|
|
34
|
+
throw new Error(`不支持的压缩格式: ${fileExtname}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
*
|
|
40
|
+
* @description: 运行项目依赖安装命令
|
|
41
|
+
* @returns
|
|
42
|
+
*/
|
|
43
|
+
function runCommand(cwd) {
|
|
44
|
+
const safeCwd = path.resolve(cwd, '../package');
|
|
45
|
+
|
|
46
|
+
if (!fs.existsSync(safeCwd)) {
|
|
47
|
+
throw new Error(`目录不存在: ${safeCwd}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const child = process.platform === 'win32'
|
|
52
|
+
? spawn('cmd.exe', ['/c', 'pnpm', 'install'], {
|
|
53
|
+
cwd: safeCwd,
|
|
54
|
+
stdio: 'inherit'
|
|
55
|
+
})
|
|
56
|
+
: spawn('npm', ['install'], {
|
|
57
|
+
cwd: safeCwd,
|
|
58
|
+
stdio: 'inherit'
|
|
59
|
+
});
|
|
60
|
+
console.log(chalk.green("\n依赖安装中..."));
|
|
61
|
+
child.on('error', reject);
|
|
62
|
+
child.on('close', code => {
|
|
63
|
+
if (code !== 0) {
|
|
64
|
+
reject(new Error(`npm install exited with code ${code}`));
|
|
65
|
+
} else {
|
|
66
|
+
console.log(chalk.green("\n依赖安装完成!"));
|
|
67
|
+
resolve();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {string} url 下载链接
|
|
75
|
+
* @param {string} targetDir 目标文件夹
|
|
76
|
+
* @param {string} fileName 保存的文件名
|
|
77
|
+
* @description: 下载文件并显示进度条
|
|
78
|
+
*/
|
|
79
|
+
export async function downloadFileToFolder(url, targetDir, fileName) {
|
|
80
|
+
// 目标文件夹确保存在
|
|
81
|
+
await fs.promises.mkdir(targetDir, { recursive: true });
|
|
82
|
+
|
|
83
|
+
const dest = path.join(targetDir, fileName);
|
|
84
|
+
|
|
85
|
+
const res = await fetch(url, {
|
|
86
|
+
headers: {
|
|
87
|
+
'User-Agent': 'node.js',
|
|
88
|
+
'Accept': 'application/octet-stream'
|
|
89
|
+
// 'Authorization': `token ${YOUR_TOKEN}`
|
|
90
|
+
},
|
|
91
|
+
redirect: 'follow', // 跟随重定向
|
|
92
|
+
});
|
|
93
|
+
if (!res.ok) throw new Error(`下载失败: ${res.status} ${res.statusText}`);
|
|
94
|
+
|
|
95
|
+
// 获取 content-length 用于进度条(如果服务器提供)
|
|
96
|
+
const total = +res.headers.get('content-length') || 0;
|
|
97
|
+
const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);
|
|
98
|
+
if (total > 0) progressBar.start(total, 0);
|
|
99
|
+
|
|
100
|
+
const fileStream = fs.createWriteStream(dest);
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
let downloaded = 0;
|
|
104
|
+
// 监听 data 更新进度
|
|
105
|
+
res.body.on('data', chunk => {
|
|
106
|
+
downloaded += chunk.length;
|
|
107
|
+
if (total > 0) progressBar.update(downloaded);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await new Promise((resolve, reject) => {
|
|
111
|
+
// 只在 res.body 上 pipe 一次,不要先消费流
|
|
112
|
+
res.body.pipe(fileStream);
|
|
113
|
+
res.body.on('error', reject);
|
|
114
|
+
fileStream.on('finish', resolve);
|
|
115
|
+
});
|
|
116
|
+
return dest;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @description 解压并安装依赖
|
|
121
|
+
*/
|
|
122
|
+
async function unCompressFileAndInstall(dir) {
|
|
123
|
+
try {
|
|
124
|
+
const entries = await fs.readdir(dir);
|
|
125
|
+
|
|
126
|
+
for (const name of entries) {
|
|
127
|
+
const filePath = path.join(dir, name);
|
|
128
|
+
const stat = await fs.stat(filePath); //返回一个 Stats 对象,这个对象包含了关于这个路径的详细信息
|
|
129
|
+
const CompressedType = ['.zip', '.tgz', '.tar', '.gz'];
|
|
130
|
+
const fileExtname = path.extname(name).toLowerCase();
|
|
131
|
+
|
|
132
|
+
if (stat.isFile() && CompressedType.includes(fileExtname)) {
|
|
133
|
+
const unzipTarget = path.dirname(filePath);
|
|
134
|
+
const unzipDir = path.join(dir, path.basename(name, fileExtname));
|
|
135
|
+
// 确保解压目标文件夹存在
|
|
136
|
+
await fs.ensureDir(unzipTarget);
|
|
137
|
+
await unCompressByType(filePath, unzipTarget, fileExtname); // 解压
|
|
138
|
+
await runCommand(unzipDir); // 运行 npm install
|
|
139
|
+
await fs.remove(filePath); // 解压后删除压缩文件
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch (err) {
|
|
143
|
+
throw new Error('\n操作失败' + err);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function getFileNameFromUrl(urlStr) {
|
|
148
|
+
const u = new URL(urlStr); //使用new URL 可以自动处理 query、hash
|
|
149
|
+
const fileName = path.basename(u.pathname);
|
|
150
|
+
return fileName;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const main_proj_remote_url = 'http://10.14.121.116:8081/repository/npm-hosted/artery5-manager-platform/-/artery5-manager-platform-1.0.2.tgz';
|
|
154
|
+
const sub_proj_remote_url = 'http://10.14.121.116:8081/repository/npm-hosted/artery5-manager-platform/-/artery5-manager-platform-1.0.2.tgz';
|
|
155
|
+
|
|
156
|
+
const program = new Command();
|
|
157
|
+
program
|
|
158
|
+
.command('create')
|
|
159
|
+
.alias('init')
|
|
160
|
+
.description('创建一个项目')
|
|
161
|
+
// .argument('<project-name>', '项目名称')
|
|
162
|
+
// .option('-f, --force', '强制覆盖,如果文件存在则强制覆盖')
|
|
163
|
+
// .option('-l, --local-path <path>', '从本地下载模版创建')
|
|
164
|
+
.action(async () => {
|
|
165
|
+
try {
|
|
166
|
+
let { projectName } = await inquirer.prompt([
|
|
167
|
+
{
|
|
168
|
+
type: 'input',
|
|
169
|
+
name: 'projectName',
|
|
170
|
+
message: '请输入项目名称',
|
|
171
|
+
default: 'my-project',
|
|
172
|
+
},
|
|
173
|
+
]);
|
|
174
|
+
let { projectType } = await inquirer.prompt([
|
|
175
|
+
{
|
|
176
|
+
type: 'select', //Inquirer(从 v13+ 开始)已经不再支持 type: 'list' 这个 prompt 类型了
|
|
177
|
+
name: 'projectType',
|
|
178
|
+
message: '请选择项目类型',
|
|
179
|
+
choices: [
|
|
180
|
+
{ name: "vue2", value: "vue2" },
|
|
181
|
+
{ name: "vue3", value: "vue3" }
|
|
182
|
+
],
|
|
183
|
+
default: 'vue3',
|
|
184
|
+
},
|
|
185
|
+
]);
|
|
186
|
+
let { downloadType } = await inquirer.prompt([
|
|
187
|
+
{
|
|
188
|
+
type: 'select',
|
|
189
|
+
name: 'downloadType',
|
|
190
|
+
message: '请选择模版下载方式',
|
|
191
|
+
choices: [
|
|
192
|
+
{ name: "本地", value: "local" },
|
|
193
|
+
{ name: "远程", value: "remote" }
|
|
194
|
+
],
|
|
195
|
+
default: 'remote',
|
|
196
|
+
},
|
|
197
|
+
]);
|
|
198
|
+
let { templateChosen } = await inquirer.prompt([
|
|
199
|
+
{
|
|
200
|
+
type: 'select',
|
|
201
|
+
name: 'templateChosen',
|
|
202
|
+
message: '请选择模板类型',
|
|
203
|
+
choices: [
|
|
204
|
+
// 'template01', 'template02'
|
|
205
|
+
{ name: "template-main", value: "template-main" },
|
|
206
|
+
{ name: "template-sub", value: "template-sub" }
|
|
207
|
+
],
|
|
208
|
+
default: 'template-main',
|
|
209
|
+
// filter: (val) => {
|
|
210
|
+
// return val.name
|
|
211
|
+
// }
|
|
212
|
+
},
|
|
213
|
+
]);
|
|
214
|
+
let { force } = await inquirer.prompt([
|
|
215
|
+
{
|
|
216
|
+
type: 'confirm',
|
|
217
|
+
name: 'force',
|
|
218
|
+
message: '是否强制覆盖',
|
|
219
|
+
},
|
|
220
|
+
]);
|
|
221
|
+
// const syncTemplate = ora('同步模版中...')
|
|
222
|
+
// syncTemplate.start()
|
|
223
|
+
const cwd = process.cwd();
|
|
224
|
+
const targetDir = `${cwd}/${projectName}`;
|
|
225
|
+
if (fs.existsSync(targetDir)) {
|
|
226
|
+
if (force) {
|
|
227
|
+
fs.removeSync(targetDir); //删除项目下已有的
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
fs.mkdirsSync(targetDir);
|
|
231
|
+
|
|
232
|
+
const templatePath = path.resolve("", templateChosen)
|
|
233
|
+
if(downloadType === 'remote') {
|
|
234
|
+
const repoMap = {
|
|
235
|
+
'template-main': main_proj_remote_url,
|
|
236
|
+
'template-sub': sub_proj_remote_url
|
|
237
|
+
};
|
|
238
|
+
const repoUrl = repoMap[templateChosen];
|
|
239
|
+
if (repoUrl) {
|
|
240
|
+
const headers = {
|
|
241
|
+
'User-Agent': 'node.js',
|
|
242
|
+
'Accept': 'application/octet-stream',
|
|
243
|
+
}
|
|
244
|
+
const fileName = getFileNameFromUrl(repoUrl);
|
|
245
|
+
await downloadFileToFolder(repoUrl, targetDir, fileName, headers);
|
|
246
|
+
}
|
|
247
|
+
} else{
|
|
248
|
+
await fs.copySync(templatePath, targetDir)
|
|
249
|
+
}
|
|
250
|
+
console.log(chalk.green("\n同步模版成功,开始解压并安装依赖..."));
|
|
251
|
+
await unCompressFileAndInstall(targetDir);
|
|
252
|
+
// syncTemplate.succeed('同步模版成功')
|
|
253
|
+
console.log(
|
|
254
|
+
chalk.green(chalk.blue.underline.bold(projectName) + ' 项目创建成功!')
|
|
255
|
+
);
|
|
256
|
+
// 退出
|
|
257
|
+
process.stdin.pause();
|
|
258
|
+
process.exit(0); // 完成后正常退出
|
|
259
|
+
} catch (e) {
|
|
260
|
+
if (e && e.name === 'ExitPromptError') {
|
|
261
|
+
console.log('\n 已取消创建!');
|
|
262
|
+
process.exit(0);
|
|
263
|
+
}
|
|
264
|
+
process.exit(1); // 遇错退出
|
|
265
|
+
}
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
program.on('--help', function () {
|
|
269
|
+
console.log('\nExamples:')
|
|
270
|
+
console.log(`artery-cli create`)
|
|
271
|
+
})
|
|
272
|
+
program.parse();
|
|
273
|
+
|
|
274
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lzx-test-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"bin": {
|
|
5
|
+
"lzx-test-cli": "bin/index.js"
|
|
6
|
+
},
|
|
7
|
+
"main": "bin/index.js",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "ts-node bin/index.ts",
|
|
11
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [],
|
|
14
|
+
"author": "",
|
|
15
|
+
"license": "ISC",
|
|
16
|
+
"description": "",
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^25.0.6",
|
|
19
|
+
"ts-node": "^10.9.2",
|
|
20
|
+
"typescript": "^5.9.3"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@types/fs-extra": "^11.0.4",
|
|
24
|
+
"chalk": "^5.6.2",
|
|
25
|
+
"cli-progress": "^3.12.0",
|
|
26
|
+
"commander": "^14.0.2",
|
|
27
|
+
"compressing": "^2.0.0",
|
|
28
|
+
"download-git-repo": "^3.0.2",
|
|
29
|
+
"fs-extra": "^11.3.3",
|
|
30
|
+
"inquirer": "^13.2.0",
|
|
31
|
+
"node-fetch": "^3.3.2",
|
|
32
|
+
"ora": "^9.0.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
Binary file
|
|
Binary file
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Visit https://aka.ms/tsconfig to read more about this file
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
// File Layout
|
|
5
|
+
// "rootDir": "./src",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
// Environment Settings
|
|
8
|
+
// See also https://aka.ms/tsconfig/module
|
|
9
|
+
"module": "commonjs",
|
|
10
|
+
"target": "esnext",
|
|
11
|
+
"types": [],
|
|
12
|
+
// For nodejs:
|
|
13
|
+
// "lib": ["esnext"],
|
|
14
|
+
// "types": ["node"],
|
|
15
|
+
// and npm install -D @types/node
|
|
16
|
+
// Other Outputs
|
|
17
|
+
"sourceMap": true,
|
|
18
|
+
"declaration": true,
|
|
19
|
+
"declarationMap": true,
|
|
20
|
+
// Stricter Typechecking Options
|
|
21
|
+
"noUncheckedIndexedAccess": true,
|
|
22
|
+
"exactOptionalPropertyTypes": true,
|
|
23
|
+
// Style Options
|
|
24
|
+
// "noImplicitReturns": true,
|
|
25
|
+
// "noImplicitOverride": true,
|
|
26
|
+
// "noUnusedLocals": true,
|
|
27
|
+
// "noUnusedParameters": true,
|
|
28
|
+
// "noFallthroughCasesInSwitch": true,
|
|
29
|
+
// "noPropertyAccessFromIndexSignature": true,
|
|
30
|
+
// Recommended Options
|
|
31
|
+
"strict": true,
|
|
32
|
+
"jsx": "react-jsx",
|
|
33
|
+
"verbatimModuleSyntax": true,
|
|
34
|
+
"isolatedModules": true,
|
|
35
|
+
"noUncheckedSideEffectImports": true,
|
|
36
|
+
"moduleDetection": "force",
|
|
37
|
+
"skipLibCheck": true,
|
|
38
|
+
},
|
|
39
|
+
"include": [
|
|
40
|
+
"/src/**/*"
|
|
41
|
+
],
|
|
42
|
+
"exclude": [
|
|
43
|
+
"node_modules",
|
|
44
|
+
"dist"
|
|
45
|
+
]
|
|
46
|
+
}
|