@ansstory/hias 1.0.4 → 1.0.5

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 CHANGED
@@ -1 +1 @@
1
- * text=auto eol=lf
1
+ * text=auto eol=lf
@@ -0,0 +1,8 @@
1
+ {
2
+ "i18n-ally.localesPaths": [
3
+ "lib/i18n/**/*",
4
+ ],
5
+ "i18n-ally.keystyle": "nested",
6
+ "i18n-ally.sourceLanguage": "zh-CN",
7
+ "i18n-ally.displayLanguage": "zh-CN",
8
+ }
package/I18N.md ADDED
@@ -0,0 +1,178 @@
1
+ # 国际化功能文档
2
+
3
+ ## 概述
4
+
5
+ hias-cli 现在支持国际化(i18n),用户可以通过命令切换 CLI 工具的显示语言。
6
+
7
+ ## 支持的语言
8
+
9
+ - **英文** (en) - 默认语言
10
+ - **简体中文** (zh-CN)
11
+
12
+ ## 使用方法
13
+
14
+ ### 1. 查看当前语言
15
+
16
+ ```bash
17
+ hias lang -l
18
+ # 或
19
+ hias lang --list
20
+ ```
21
+
22
+ ### 2. 切换语言
23
+
24
+ **切换到中文:**
25
+
26
+ ```bash
27
+ hias lang zh-CN
28
+ ```
29
+
30
+ **切换到英文:**
31
+
32
+ ```bash
33
+ hias lang en
34
+ ```
35
+
36
+ ### 3. 语言设置的持久化
37
+
38
+ 用户选择的语言会被保存到 `~/.hias-cli/config.json`,下次使用 CLI 工具时会自动使用保存的语言设置。
39
+
40
+ ```json
41
+ {
42
+ "language": "zh-CN"
43
+ }
44
+ ```
45
+
46
+ ## 技术实现
47
+
48
+ ### 依赖库
49
+
50
+ - **i18next** - 专业的国际化库,用于管理多语言翻译
51
+
52
+ ### 项目结构
53
+
54
+ ```
55
+ lib/
56
+ ├── i18n/
57
+ │ ├── index.js # i18n 初始化模块
58
+ │ ├── store.js # 语言配置存储和管理
59
+ │ └── resources/
60
+ │ ├── en.json # 英文翻译资源
61
+ │ └── zh-CN.json # 中文翻译资源
62
+ ├── core/
63
+ │ ├── lang.js # 语言切换命令处理
64
+ │ ├── commander.js # 命令定义(已集成 i18n)
65
+ │ ├── action.js # 命令处理(已集成 i18n)
66
+ │ ├── download.js # 下载处理(已集成 i18n)
67
+ │ └── help.js # 帮助信息(已集成 i18n)
68
+ └── index.js # 入口文件(初始化 i18n)
69
+ ```
70
+
71
+ ### 配置文件位置
72
+
73
+ `~/.hias-cli/config.json` - 存储用户的语言偏好
74
+
75
+ ## 示例
76
+
77
+ ### 场景 1:首次使用
78
+
79
+ 用户首次运行 CLI 工具,默认显示英文:
80
+
81
+ ```bash
82
+ $ hias -h
83
+ Commands:
84
+ create|crt <project> [other...] create project. For example: hias create airbnb
85
+ adv <vuecpnname> [...others] add vue component into a folder...
86
+ ...
87
+ lang [options] [language] set CLI language. For example: hias lang zh-CN or hias lang en
88
+ ```
89
+
90
+ ### 场景 2:切换到中文
91
+
92
+ ```bash
93
+ $ hias lang zh-CN
94
+ 语言已设置为: 简体中文
95
+
96
+ $ hias -h
97
+ Commands:
98
+ create|crt <project> [other...] 创建项目。示例: hias create airbnb
99
+ adv <vuecpnname> [...others] 将 Vue 组件添加到文件夹,示例...
100
+ ...
101
+ lang [options] [language] 设置 CLI 语言。示例: hias lang zh-CN 或 hias lang en
102
+ ```
103
+
104
+ ### 场景 3:查看当前语言
105
+
106
+ ```bash
107
+ $ hias lang -l
108
+ 当前语言: 简体中文
109
+ ```
110
+
111
+ ### 场景 4:切换回英文
112
+
113
+ ```bash
114
+ $ hias lang en
115
+ Language has been set to: English
116
+
117
+ $ hias lang -l
118
+ Current language: English
119
+ ```
120
+
121
+ ## 扩展国际化
122
+
123
+ 要添加新的语言支持,只需:
124
+
125
+ 1. 在 `lib/i18n/resources/` 目录中创建新的翻译文件(如 `es.json`)
126
+ 2. 在 `lib/i18n/store.js` 中更新 `SUPPORTED_LANGUAGES` 数组
127
+ 3. 在新的翻译文件中翻译所有的消息键
128
+
129
+ ### 翻译文件结构
130
+
131
+ 参考 `lib/i18n/resources/en.json` 或 `zh-CN.json`:
132
+
133
+ ```json
134
+ {
135
+ "commands": {
136
+ "create": "...",
137
+ "adv": "...",
138
+ "lang": "..."
139
+ },
140
+ "options": {
141
+ "dest": "..."
142
+ },
143
+ "prompts": {
144
+ "selectTemplate": "..."
145
+ },
146
+ "messages": {
147
+ "downloading": "...",
148
+ "langSet": "...",
149
+ "langCurrent": "..."
150
+ },
151
+ "languages": {
152
+ "en": "English",
153
+ "zh-CN": "简体中文"
154
+ }
155
+ }
156
+ ```
157
+
158
+ ## 注意事项
159
+
160
+ - 语言设置是全局的,会影响所有 CLI 命令的输出
161
+ - 配置文件会在用户的主目录下创建 `.hias-cli` 目录
162
+ - 如果配置文件损坏,CLI 会自动使用默认语言(英文)
163
+ - 当前支持的语言代码符合 BCP 47 标准
164
+
165
+ ## 故障排除
166
+
167
+ ### 问题:语言没有切换
168
+
169
+ - **解决方案**:检查 `~/.hias-cli/config.json` 是否存在且有写入权限
170
+ - 尝试手动删除配置文件,让 CLI 重新创建
171
+
172
+ ### 问题:出现乱码
173
+
174
+ - **解决方案**:确保你的终端支持 UTF-8 编码
175
+
176
+ ### 问题:某些消息仍然是英文
177
+
178
+ - **解决方案**:这可能是第三方库输出的信息,这些信息不受 i18n 控制
package/LICENSE CHANGED
@@ -1,22 +1,22 @@
1
- The MIT License (MIT)
2
-
3
- Copyright © 2025
4
-
5
-
6
- Permission is hereby granted, free of charge, to any person obtaining a copy
7
- of this software and associated documentation files (the “Software”), to deal
8
- in the Software without restriction, including without limitation the rights
9
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- copies of the Software, and to permit persons to whom the Software is
11
- furnished to do so, subject to the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be included in
14
- all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
- THE SOFTWARE.
1
+ The MIT License (MIT)
2
+
3
+ Copyright © 2025
4
+
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the “Software”), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
package/README.md CHANGED
@@ -1,11 +1,13 @@
1
- # hias-cli
2
- ```sh
3
- npm i hias -g
4
- hias -h
5
- hias create xxx
6
- hias adv Demo -d src/components
7
- hias adr Demo -d src/components
8
- hias adrt Demo -d src/components
9
- hias adrd useDemoStore -d src/store/modules
10
- hias adrdt useDemoStore -d src/store/modules
11
- ```
1
+ # hias-cli
2
+ ```sh
3
+ npm i hias -g
4
+ hias -h
5
+ hias create xxx
6
+ hias adv Demo -d src/components
7
+ hias adr Demo -d src/components
8
+ hias adrt Demo -d src/components
9
+ hias adrd useDemoStore -d src/store/modules
10
+ hias adrdt useDemoStore -d src/store/modules
11
+ hias close-port 8076 8077
12
+ hias close-port 8076,8077
13
+ ```
package/lib/config.js CHANGED
@@ -1,18 +1,22 @@
1
- const foramworkUrl = {
2
- 'vue2': 'https://gitee.com/AnsStory/hias-vue2-template.git',
3
- 'vue3': 'https://gitee.com/AnsStory/hias-vue3-template.git',
4
- 'vue-ts': 'https://gitee.com/AnsStory/hias-vue-ts-template.git',
5
- 'uni-vite': 'https://gitee.com/AnsStory/hias-uni-vite.git',
6
- 'nuxt-web': 'https://gitee.com/AnsStory/hias-nuxt-web-tmp.git',
7
- 'vue-screen': 'https://gitee.com/AnsStory/hias-vite-screen.git',
8
- 'react': 'https://gitee.com/AnsStory/hias-react-template.git',
9
- 'react-ts': 'https://gitee.com/AnsStory/hias-react-ts-template.git',
10
- }
11
- const framwork = Object.keys(foramworkUrl)
12
-
13
- module.exports = {
14
- // 可选择的框架
15
- framwork,
16
- // 框架对应的下载地址
17
- foramworkUrl,
18
- }
1
+ // 项目配置文件 - 定义支持的框架模板和对应的下载地址
2
+ // 各框架模板的 Gitee 仓库地址映射表
3
+ const foramworkUrl = {
4
+ vue2: 'https://gitee.com/AnsStory/hias-vue2-template.git',
5
+ vue3: 'https://gitee.com/AnsStory/hias-vue3-template.git',
6
+ 'vue-ts': 'https://gitee.com/AnsStory/hias-vue-ts-template.git',
7
+ 'uni-vite': 'https://gitee.com/AnsStory/hias-uni-vite.git',
8
+ 'nuxt-web': 'https://gitee.com/AnsStory/hias-nuxt-web-tmp.git',
9
+ 'vue-screen': 'https://gitee.com/AnsStory/hias-vite-screen.git',
10
+ react: 'https://gitee.com/AnsStory/hias-react-template.git',
11
+ 'react-ts': 'https://gitee.com/AnsStory/hias-react-ts-template.git',
12
+ }
13
+
14
+ // 从 foramworkUrl 对象提取所有支持的框架名称
15
+ const framwork = Object.keys(foramworkUrl)
16
+
17
+ module.exports = {
18
+ // 可选择的框架列表
19
+ framwork,
20
+ // 框架对应的下载地址映射表
21
+ foramworkUrl,
22
+ }
@@ -1,68 +1,95 @@
1
- const inquirer = require('inquirer')
2
- const chalk = require('chalk')
3
- const { program } = require('commander')
4
- var config = require('../config')
5
- var downloadFun = require('./download')
6
- const compileEjs = require('../utils/compile-ejs')
7
- const writeFile = require('../utils/write-file')
8
- const beginAction = async (project, args) => {
9
- // 命令行的执行逻辑代码
10
- // console.log(project);
11
- // console.log(args);
12
- const answer = await inquirer.prompt([
13
- {
14
- type: 'list',
15
- name: 'framwork',
16
- choices: config.framwork,
17
- message: 'Please select the template.',
18
- },
19
- ])
20
- // console.log(answer);
21
- // 下载代码模板
22
- downloadFun(config.foramworkUrl[answer.framwork], project)
23
- }
24
-
25
- async function addComponentAction(cpnName, temPath, fileType) {
26
- // 边界处理: 若为 .\xx\xx\ 则转换为 ./xx/xx/
27
- const dest = program.opts().dest.replace(/\\/g, '/') || 'src/components'
28
- // 边界处理: 若为 ./xx/xx 则转换为 ./xx/xx/
29
- const path = /[\\/]/.test(dest.at(-1)) ? dest : `${dest}/`
30
-
31
- const [folderName = 'index'] = dest.match(/[^\\/]+(?=\/?$)/)
32
- const name = cpnName === 'index' ? folderName : cpnName
33
-
34
- const result = await compileEjs(temPath, {
35
- name: name,
36
- lowername: name.toLowerCase(),
37
- })
38
-
39
- const filePath = `${path}${cpnName}.${fileType}`
40
-
41
- await writeFile(filePath, result)
42
- console.log(chalk.green.bold('Component creation was successful:'), chalk.blue.bold(`${cpnName}.${fileType}`), chalk.underline(filePath))
43
- }
44
-
45
- async function addVueComponentAction(cpnName) {
46
- await addComponentAction(cpnName, 'component.vue.ejs', 'vue')
47
- }
48
- async function addReactComponentAction(cpnName) {
49
- await addComponentAction(cpnName, 'component.jsx.ejs', 'jsx')
50
- }
51
- async function addReactTsComponentAction(cpnName) {
52
- await addComponentAction(cpnName, 'component.tsx.ejs', 'tsx')
53
- }
54
- async function addReduxStoreAction(cpnName) {
55
- await addComponentAction(cpnName, 'reduxStore.jsx.ejs', 'jsx')
56
- }
57
- async function addReduxTsStoreAction(cpnName) {
58
- await addComponentAction(cpnName, 'reduxTsStore.tsx.ejs', 'tsx')
59
- }
60
-
61
- module.exports = {
62
- beginAction,
63
- addVueComponentAction,
64
- addReactComponentAction,
65
- addReactTsComponentAction,
66
- addReduxStoreAction,
67
- addReduxTsStoreAction,
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
+ }