@ansstory/hias 1.0.4 → 1.0.6
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/.gitattributes +1 -1
- package/.vscode/settings.json +8 -0
- package/LICENSE +22 -22
- package/README.md +533 -11
- package/README.zh-CN.md +531 -0
- package/lib/config.js +22 -18
- package/lib/core/action.js +95 -68
- package/lib/core/close-port.js +173 -0
- package/lib/core/commander.js +106 -40
- package/lib/core/download.js +32 -19
- package/lib/core/help.js +17 -6
- package/lib/core/lang.js +50 -0
- package/lib/core/translate.js +1049 -0
- package/lib/extractors/index.js +694 -0
- package/lib/i18n/index.js +77 -0
- package/lib/i18n/resources/en.json +91 -0
- package/lib/i18n/resources/zh-CN.json +91 -0
- package/lib/i18n/store.js +85 -0
- package/lib/index.js +14 -6
- package/lib/template/component.jsx.ejs +11 -11
- package/lib/template/component.tsx.ejs +12 -12
- package/lib/template/component.vue.ejs +13 -13
- package/lib/template/reduxStore.jsx.ejs +16 -16
- package/lib/template/reduxTsStore.tsx.ejs +22 -22
- package/lib/utils/baidu-translate.js +78 -0
- package/lib/utils/compile-ejs.js +28 -21
- package/lib/utils/tencent-translate.js +187 -0
- package/lib/utils/translation-cache.js +73 -0
- package/lib/utils/write-file.js +16 -10
- package/package.json +11 -3
- package/test/close-port.test.js +88 -0
- package/test/translate.test.js +545 -0
package/lib/core/action.js
CHANGED
|
@@ -1,68 +1,95 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
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
|
-
const
|
|
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
|
-
|
|
1
|
+
// 命令行操作处理文件 - 处理各种命令的具体执行逻辑
|
|
2
|
+
|
|
3
|
+
const inquirer = require('inquirer')
|
|
4
|
+
const chalk = require('chalk')
|
|
5
|
+
const { program } = require('commander')
|
|
6
|
+
var config = require('../config')
|
|
7
|
+
var downloadFun = require('./download')
|
|
8
|
+
const compileEjs = require('../utils/compile-ejs')
|
|
9
|
+
const writeFile = require('../utils/write-file')
|
|
10
|
+
const i18n = require('../i18n')
|
|
11
|
+
|
|
12
|
+
// 创建项目的主处理函数
|
|
13
|
+
const beginAction = async (project, args) => {
|
|
14
|
+
// 获取翻译函数
|
|
15
|
+
const t = i18n.getT()
|
|
16
|
+
|
|
17
|
+
// 使用 inquirer 交互式命令行提示用户选择框架
|
|
18
|
+
const answer = await inquirer.prompt([
|
|
19
|
+
{
|
|
20
|
+
type: 'list',
|
|
21
|
+
name: 'framwork',
|
|
22
|
+
choices: config.framwork,
|
|
23
|
+
message: t('prompts.selectTemplate'),
|
|
24
|
+
},
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
// 调用下载函数获取对应框架的模板
|
|
28
|
+
downloadFun(config.foramworkUrl[answer.framwork], project)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 通用的组件/Store 添加处理函数
|
|
32
|
+
async function addComponentAction(cpnName, temPath, fileType) {
|
|
33
|
+
// 获取翻译函数
|
|
34
|
+
const t = i18n.getT()
|
|
35
|
+
|
|
36
|
+
// 边界处理:将反斜杠转换为正斜杠(处理 Windows 路径格式)
|
|
37
|
+
const dest = program.opts().dest.replace(/\\/g, '/') || 'src/components'
|
|
38
|
+
|
|
39
|
+
// 边界处理:确保路径末尾带有斜杠
|
|
40
|
+
const path = /[\\/]/.test(dest.at(-1)) ? dest : `${dest}/`
|
|
41
|
+
|
|
42
|
+
// 从目标路径中提取文件夹名称(用作默认组件名)
|
|
43
|
+
const [folderName = 'index'] = dest.match(/[^\\/]+(?=\/?$)/)
|
|
44
|
+
// 如果组件名为 'index',使用文件夹名作为实际组件名
|
|
45
|
+
const name = cpnName === 'index' ? folderName : cpnName
|
|
46
|
+
|
|
47
|
+
// 使用 EJS 模板引擎编译模板文件
|
|
48
|
+
const result = await compileEjs(temPath, {
|
|
49
|
+
name: name,
|
|
50
|
+
lowername: name.toLowerCase(),
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// 拼接完整的文件路径
|
|
54
|
+
const filePath = `${path}${cpnName}.${fileType}`
|
|
55
|
+
|
|
56
|
+
// 将编译后的内容写入文件
|
|
57
|
+
await writeFile(filePath, result)
|
|
58
|
+
|
|
59
|
+
// 输出成功提示信息
|
|
60
|
+
console.log(chalk.green.bold(t('messages.componentCreated')), chalk.blue.bold(`${cpnName}.${fileType}`), chalk.underline(filePath))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 添加 Vue 组件
|
|
64
|
+
async function addVueComponentAction(cpnName) {
|
|
65
|
+
await addComponentAction(cpnName, 'component.vue.ejs', 'vue')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 添加 React JSX 组件
|
|
69
|
+
async function addReactComponentAction(cpnName) {
|
|
70
|
+
await addComponentAction(cpnName, 'component.jsx.ejs', 'jsx')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 添加 React TSX 组件
|
|
74
|
+
async function addReactTsComponentAction(cpnName) {
|
|
75
|
+
await addComponentAction(cpnName, 'component.tsx.ejs', 'tsx')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 添加 Redux JSX Store
|
|
79
|
+
async function addReduxStoreAction(cpnName) {
|
|
80
|
+
await addComponentAction(cpnName, 'reduxStore.jsx.ejs', 'jsx')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 添加 Redux TSX Store
|
|
84
|
+
async function addReduxTsStoreAction(cpnName) {
|
|
85
|
+
await addComponentAction(cpnName, 'reduxTsStore.tsx.ejs', 'tsx')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = {
|
|
89
|
+
beginAction,
|
|
90
|
+
addVueComponentAction,
|
|
91
|
+
addReactComponentAction,
|
|
92
|
+
addReactTsComponentAction,
|
|
93
|
+
addReduxStoreAction,
|
|
94
|
+
addReduxTsStoreAction,
|
|
95
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// 关闭端口命令处理文件 - 查找并结束占用指定端口的进程
|
|
2
|
+
|
|
3
|
+
const { exec: execWithCallback } = require('child_process')
|
|
4
|
+
const { promisify } = require('util')
|
|
5
|
+
|
|
6
|
+
// 将 Node 回调风格的 exec 转换为 Promise,方便 async/await 调用
|
|
7
|
+
const defaultExec = promisify(execWithCallback)
|
|
8
|
+
|
|
9
|
+
// 端口参数标准化:校验端口必须是 1-65535 之间的数字
|
|
10
|
+
function normalizePort(port) {
|
|
11
|
+
const value = String(port || '').trim()
|
|
12
|
+
|
|
13
|
+
if (!/^\d+$/.test(value)) {
|
|
14
|
+
throw new Error('Invalid port. Port must be a number between 1 and 65535.')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const number = Number(value)
|
|
18
|
+
if (number < 1 || number > 65535) {
|
|
19
|
+
throw new Error('Invalid port. Port must be a number between 1 and 65535.')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 解析命令行端口输入,支持空格分隔和逗号分隔两种方式
|
|
26
|
+
function parsePortInputs(inputs) {
|
|
27
|
+
return unique(
|
|
28
|
+
inputs
|
|
29
|
+
.flatMap((input) => String(input).split(','))
|
|
30
|
+
.map((port) => port.trim())
|
|
31
|
+
.filter(Boolean)
|
|
32
|
+
.map(normalizePort),
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 从 netstat 的本地地址字段中提取端口号
|
|
37
|
+
function getLocalPort(localAddress) {
|
|
38
|
+
const match = String(localAddress).match(/:(\d+)$/)
|
|
39
|
+
return match ? Number(match[1]) : null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 数组去重,保持原有顺序
|
|
43
|
+
function unique(values) {
|
|
44
|
+
return Array.from(new Set(values))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 解析 Windows netstat 输出,提取占用目标端口的进程 PID
|
|
48
|
+
function parsePidsFromWindowsNetstat(output, port) {
|
|
49
|
+
return unique(
|
|
50
|
+
String(output)
|
|
51
|
+
.split(/\r?\n/)
|
|
52
|
+
.map((line) => line.trim())
|
|
53
|
+
.filter(Boolean)
|
|
54
|
+
.map((line) => line.split(/\s+/))
|
|
55
|
+
.filter((parts) => {
|
|
56
|
+
const protocol = parts[0]
|
|
57
|
+
const localAddress = parts[1]
|
|
58
|
+
const state = parts[parts.length - 2]
|
|
59
|
+
|
|
60
|
+
// 只处理本地端口匹配的连接
|
|
61
|
+
if (getLocalPort(localAddress) !== port) {
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// TCP 只结束监听进程,UDP 没有 LISTENING 状态,直接按端口匹配
|
|
66
|
+
return protocol === 'UDP' || (protocol === 'TCP' && state === 'LISTENING')
|
|
67
|
+
})
|
|
68
|
+
.map((parts) => parts[parts.length - 1])
|
|
69
|
+
.filter((pid) => /^\d+$/.test(pid)),
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 解析类 Unix 系统 lsof 输出,提取进程 PID
|
|
74
|
+
function parsePidsFromPosixLsof(output) {
|
|
75
|
+
return unique(
|
|
76
|
+
String(output)
|
|
77
|
+
.split(/\r?\n/)
|
|
78
|
+
.map((line) => line.trim())
|
|
79
|
+
.filter((line) => /^\d+$/.test(line)),
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 根据当前系统查找占用端口的进程 PID
|
|
84
|
+
async function findPortPids(port, options) {
|
|
85
|
+
const exec = options.exec || defaultExec
|
|
86
|
+
const platform = options.platform || process.platform
|
|
87
|
+
|
|
88
|
+
// Windows 使用 netstat 查询所有端口,再在 Node 内部解析过滤
|
|
89
|
+
if (platform === 'win32') {
|
|
90
|
+
const { stdout } = await exec('netstat -ano')
|
|
91
|
+
return parsePidsFromWindowsNetstat(stdout, port)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// macOS/Linux 使用 lsof 查询端口;如果系统未安装 lsof 或无占用,则视为无进程
|
|
95
|
+
try {
|
|
96
|
+
const { stdout } = await exec(`lsof -ti :${port}`)
|
|
97
|
+
return parsePidsFromPosixLsof(stdout)
|
|
98
|
+
} catch (err) {
|
|
99
|
+
return []
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 根据平台结束指定 PID
|
|
104
|
+
async function killPid(pid, options) {
|
|
105
|
+
const exec = options.exec || defaultExec
|
|
106
|
+
const platform = options.platform || process.platform
|
|
107
|
+
const command = platform === 'win32' ? `taskkill /F /PID ${pid}` : `kill -9 ${pid}`
|
|
108
|
+
|
|
109
|
+
await exec(command)
|
|
110
|
+
return pid
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 关闭单个端口:查找占用进程并逐个结束
|
|
114
|
+
async function closePort(port, options = {}) {
|
|
115
|
+
const normalizedPort = normalizePort(port)
|
|
116
|
+
const pids = await findPortPids(normalizedPort, options)
|
|
117
|
+
const killed = []
|
|
118
|
+
|
|
119
|
+
for (const pid of pids) {
|
|
120
|
+
killed.push(await killPid(pid, options))
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
port: normalizedPort,
|
|
125
|
+
pids,
|
|
126
|
+
killed,
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 关闭多个端口:先解析输入,再按端口顺序逐个处理
|
|
131
|
+
async function closePorts(portInputs, options = {}) {
|
|
132
|
+
const ports = parsePortInputs(portInputs)
|
|
133
|
+
const results = []
|
|
134
|
+
|
|
135
|
+
for (const port of ports) {
|
|
136
|
+
results.push(await closePort(port, options))
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return results
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// CLI action:执行关闭端口命令并输出结果
|
|
143
|
+
async function handleClosePortAction(ports) {
|
|
144
|
+
const chalk = require('chalk')
|
|
145
|
+
const i18n = require('../i18n')
|
|
146
|
+
const t = i18n.getT()
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const results = await closePorts(ports)
|
|
150
|
+
|
|
151
|
+
// 每个端口单独输出处理结果,方便用户定位具体端口状态
|
|
152
|
+
for (const result of results) {
|
|
153
|
+
if (result.killed.length === 0) {
|
|
154
|
+
console.log(chalk.yellow.bold(t('messages.closePortNoProcess', { port: result.port })))
|
|
155
|
+
continue
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log(chalk.green.bold(t('messages.closePortSuccess', { port: result.port, pids: result.killed.join(', ') })))
|
|
159
|
+
}
|
|
160
|
+
} catch (err) {
|
|
161
|
+
console.log(chalk.red.bold(err.message))
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = {
|
|
166
|
+
closePort,
|
|
167
|
+
closePorts,
|
|
168
|
+
handleClosePortAction,
|
|
169
|
+
normalizePort,
|
|
170
|
+
parsePortInputs,
|
|
171
|
+
parsePidsFromPosixLsof,
|
|
172
|
+
parsePidsFromWindowsNetstat,
|
|
173
|
+
}
|
package/lib/core/commander.js
CHANGED
|
@@ -1,40 +1,106 @@
|
|
|
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
|
-
program
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
1
|
+
// 命令行指令定义文件 - 配置 CLI 的所有可用命令
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk')
|
|
4
|
+
const i18n = require('../i18n')
|
|
5
|
+
const {
|
|
6
|
+
beginAction,
|
|
7
|
+
addVueComponentAction,
|
|
8
|
+
addReactComponentAction,
|
|
9
|
+
addReactTsComponentAction,
|
|
10
|
+
addReduxStoreAction,
|
|
11
|
+
addReduxTsStoreAction,
|
|
12
|
+
} = require('./action')
|
|
13
|
+
const { handleLanguageAction } = require('./lang')
|
|
14
|
+
const { handleClosePortAction } = require('./close-port')
|
|
15
|
+
const { handleTranslateFileAction, handleTranslateFolderAction, handleSettingAction, handleGlobalSettingAction, handleRollbackAction, handleGitignoreAction } = require('./translate')
|
|
16
|
+
|
|
17
|
+
// 注册所有命令行指令
|
|
18
|
+
const beginCommander = function (program) {
|
|
19
|
+
// 获取翻译函数
|
|
20
|
+
const t = i18n.getT()
|
|
21
|
+
|
|
22
|
+
console.log()
|
|
23
|
+
|
|
24
|
+
// 创建项目命令:下载框架模板
|
|
25
|
+
program.command('create <project> [other...]').alias('crt').description(t('commands.create')).action(beginAction)
|
|
26
|
+
|
|
27
|
+
// 添加 Vue 组件命令
|
|
28
|
+
program.command('adv <vuecpnname> [...others]').description(t('commands.adv')).action(addVueComponentAction)
|
|
29
|
+
|
|
30
|
+
// 添加 React JSX 组件命令
|
|
31
|
+
program.command('adr <reactcpnname> [...others]').description(t('commands.adr')).action(addReactComponentAction)
|
|
32
|
+
|
|
33
|
+
// 添加 React TSX 组件命令
|
|
34
|
+
program.command('adrt <reactcpnname> [...others]').description(t('commands.adrt')).action(addReactTsComponentAction)
|
|
35
|
+
|
|
36
|
+
// 添加 Redux JSX Store 命令
|
|
37
|
+
program.command('adrd <reactcpnname> [...others]').description(t('commands.adrd')).action(addReduxStoreAction)
|
|
38
|
+
|
|
39
|
+
// 添加 Redux TSX Store 命令
|
|
40
|
+
program.command('adrdt <reactcpnname> [...others]').description(t('commands.adrdt')).action(addReduxTsStoreAction)
|
|
41
|
+
|
|
42
|
+
// 语言切换命令
|
|
43
|
+
program.command('lang [language]').description(t('commands.lang')).option('-l --list', 'show current language').action(handleLanguageAction)
|
|
44
|
+
|
|
45
|
+
// 关闭端口命令
|
|
46
|
+
program.command('close-port <ports...>').description(t('commands.closePort')).action(handleClosePortAction)
|
|
47
|
+
|
|
48
|
+
// 翻译文件命令:将单个文件中的中文替换为国际化调用并生成语言包
|
|
49
|
+
program
|
|
50
|
+
.command('tf [file] [name]')
|
|
51
|
+
.description(t('commands.translateFile'))
|
|
52
|
+
.option('-n, --name <name>', t('options.translateName'))
|
|
53
|
+
.option('--dry-run', 'preview changes without modifying files')
|
|
54
|
+
.option('--show-extractions', 'list all extracted Chinese texts without translating')
|
|
55
|
+
.option('-v, --verbose', 'verbose output with individual file logs')
|
|
56
|
+
.action((file, name, options) => {
|
|
57
|
+
name = name || options.name
|
|
58
|
+
options.name = name
|
|
59
|
+
if (!file) {
|
|
60
|
+
console.log(chalk.red(t('messages.translateFileRequired')))
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
handleTranslateFileAction(file, options)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// 翻译文件夹命令:递归翻译文件夹内所有支持的文件
|
|
67
|
+
program
|
|
68
|
+
.command('tfo [folder] [name]')
|
|
69
|
+
.description(t('commands.translateFolder'))
|
|
70
|
+
.option('-n, --name <name>', t('options.translateName'))
|
|
71
|
+
.option('--dry-run', 'preview changes without modifying files')
|
|
72
|
+
.option('--show-extractions', 'list all extracted Chinese texts without translating')
|
|
73
|
+
.option('--exclude <pattern>', 'exclude files matching glob pattern (e.g. **/*.test.js)', (val, acc) => acc.concat([val]), [])
|
|
74
|
+
.option('-v, --verbose', 'verbose output with individual file logs')
|
|
75
|
+
.action((folder, name, options) => {
|
|
76
|
+
name = name || options.name
|
|
77
|
+
options.name = name
|
|
78
|
+
if (!folder) {
|
|
79
|
+
console.log(chalk.red(t('messages.translateFolderRequired')))
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
handleTranslateFolderAction(folder, options)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
// 生成翻译配置文件(项目级)
|
|
86
|
+
program.command('setting').description(t('commands.setting')).action(handleSettingAction)
|
|
87
|
+
|
|
88
|
+
// 生成全局翻译配置文件
|
|
89
|
+
program.command('globalsetting').description(t('commands.globalSetting')).action(handleGlobalSettingAction)
|
|
90
|
+
|
|
91
|
+
// 将 .hias 添加到 .gitignore
|
|
92
|
+
program.command('gitignore').description(t('commands.gitignore')).action(handleGitignoreAction)
|
|
93
|
+
|
|
94
|
+
// 回滚上一次翻译操作
|
|
95
|
+
program
|
|
96
|
+
.command('rollback')
|
|
97
|
+
.description(t('commands.rollback'))
|
|
98
|
+
.option('-l, --list', 'list available backups')
|
|
99
|
+
.option('--name <name>', 'specific backup name to rollback (default: latest)')
|
|
100
|
+
.option('--keep <count>', 'keep only N most recent backups, remove older ones')
|
|
101
|
+
.action((options) => {
|
|
102
|
+
handleRollbackAction(options)
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = beginCommander
|
package/lib/core/download.js
CHANGED
|
@@ -1,19 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
1
|
+
// 项目模板下载处理文件 - 从 Git 仓库下载框架模板
|
|
2
|
+
|
|
3
|
+
const download = require('download-git-repo')
|
|
4
|
+
const ora = require('ora')
|
|
5
|
+
const chalk = require('chalk')
|
|
6
|
+
const i18n = require('../i18n')
|
|
7
|
+
|
|
8
|
+
// 下载框架模板函数
|
|
9
|
+
const downloadFun = function (url, project) {
|
|
10
|
+
// 获取翻译函数
|
|
11
|
+
const t = i18n.getT()
|
|
12
|
+
|
|
13
|
+
// 创建加载动画
|
|
14
|
+
const spinner = ora().start()
|
|
15
|
+
spinner.text = t('messages.downloading')
|
|
16
|
+
|
|
17
|
+
// 使用 download-git-repo 库下载 Git 仓库
|
|
18
|
+
download('direct:' + url, project, { clone: true }, (err) => {
|
|
19
|
+
if (!err) {
|
|
20
|
+
// 下载成功,停止加载动画并显示成功提示
|
|
21
|
+
spinner.succeed(t('messages.downloadSuccess'))
|
|
22
|
+
console.log(chalk.blue.bold(t('messages.youRun')))
|
|
23
|
+
console.log(chalk.blue.bold('cd ' + project))
|
|
24
|
+
console.log(chalk.blue.bold('code .'))
|
|
25
|
+
} else {
|
|
26
|
+
// 下载失败,显示错误信息
|
|
27
|
+
spinner.fail(`${t('messages.downloadError')}: ${err}`)
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = downloadFun
|
package/lib/core/help.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
// 帮助信息配置 - 定义 CLI 版本号和全局选项
|
|
2
|
+
|
|
3
|
+
const i18n = require('../i18n')
|
|
4
|
+
|
|
5
|
+
const beginHelp = function (program) {
|
|
6
|
+
// 获取翻译函数
|
|
7
|
+
const t = i18n.getT()
|
|
8
|
+
|
|
9
|
+
// 从 package.json 中读取版本号
|
|
10
|
+
const version = require('../../package.json').version
|
|
11
|
+
// 设置版本号命令
|
|
12
|
+
program.version(version, '-v --version')
|
|
13
|
+
// 定义全局目标目录选项
|
|
14
|
+
program.option('-d --dest <dest>', t('options.dest'))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = beginHelp
|
package/lib/core/lang.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// 语言切换命令处理 - 处理用户设置 CLI 语言的操作
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk')
|
|
4
|
+
const i18n = require('../i18n')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 处理语言切换命令
|
|
8
|
+
* @param {string} language - 语言代码
|
|
9
|
+
* @param {object} options - 命令选项
|
|
10
|
+
*/
|
|
11
|
+
const handleLanguageAction = (language, options) => {
|
|
12
|
+
const t = i18n.getT()
|
|
13
|
+
|
|
14
|
+
// 查看当前语言
|
|
15
|
+
if (options.list || options.l) {
|
|
16
|
+
const currentLang = i18n.getCurrentLanguage()
|
|
17
|
+
const langName = t(`languages.${currentLang}`)
|
|
18
|
+
console.log(chalk.blue.bold(t('messages.langCurrent', { language: langName })))
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 如果没有提供语言参数
|
|
23
|
+
if (!language) {
|
|
24
|
+
console.log(chalk.yellow.bold('Usage: hias lang <language> | hias lang -l'))
|
|
25
|
+
console.log(chalk.gray(`Supported languages: ${i18n.getSupportedLanguages().join(', ')}`))
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 验证语言是否支持
|
|
30
|
+
if (!i18n.SUPPORTED_LANGUAGES.includes(language)) {
|
|
31
|
+
const supportedLangs = i18n
|
|
32
|
+
.getSupportedLanguages()
|
|
33
|
+
.map((lang) => `${lang} (${t(`languages.${lang}`)})`)
|
|
34
|
+
.join(', ')
|
|
35
|
+
console.log(chalk.red.bold(t('messages.invalidLanguage', { languages: supportedLangs })))
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 设置语言
|
|
40
|
+
if (i18n.changeLanguage(language)) {
|
|
41
|
+
const langName = t(`languages.${language}`)
|
|
42
|
+
console.log(chalk.green.bold(t('messages.langSet', { language: langName })))
|
|
43
|
+
} else {
|
|
44
|
+
console.log(chalk.red.bold('Failed to set language'))
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = {
|
|
49
|
+
handleLanguageAction,
|
|
50
|
+
}
|