@wangzhanpeng996/wzp 0.1.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/LICENSE +21 -0
- package/README.md +119 -0
- package/bin/wzp.js +404 -0
- package/package.json +29 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# wzp
|
|
2
|
+
|
|
3
|
+
`wzp` 是一个个人命令行工具箱。安装后统一使用 `wzp <命令>` 调用不同功能。
|
|
4
|
+
|
|
5
|
+
当前已有功能:
|
|
6
|
+
|
|
7
|
+
- `wzp gitee-create`:从一个源仓库克隆项目,删除原 `.git`,自动创建新的 Gitee 仓库,并重新绑定 remote。
|
|
8
|
+
- `wzp create`:`wzp gitee-create` 的兼容别名。
|
|
9
|
+
- `wzp kill`:快速释放本机指定端口,例如 `wzp kill 8080`。
|
|
10
|
+
- `wzp kill-port`:`wzp kill` 的兼容别名。
|
|
11
|
+
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
本机开发调试:
|
|
15
|
+
|
|
16
|
+
```powershell
|
|
17
|
+
npm link
|
|
18
|
+
wzp --help
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
发布到 npm 后:
|
|
22
|
+
|
|
23
|
+
```powershell
|
|
24
|
+
npm i -g @wangzhanpeng996/wzp
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## gitee-create
|
|
28
|
+
|
|
29
|
+
先在当前 PowerShell 会话设置 Gitee 访问令牌:
|
|
30
|
+
|
|
31
|
+
```powershell
|
|
32
|
+
$env:GITEE_TOKEN="你的Gitee访问令牌"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
然后在你希望生成项目的目录里执行:
|
|
36
|
+
|
|
37
|
+
```powershell
|
|
38
|
+
wzp gitee-create https://gitee.com/wzplxt/web1.git my-new-repo
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
也可以使用具名参数:
|
|
42
|
+
|
|
43
|
+
```powershell
|
|
44
|
+
wzp gitee-create `
|
|
45
|
+
--source https://gitee.com/wzplxt/web1.git `
|
|
46
|
+
--repo my-new-repo `
|
|
47
|
+
--description "我的新项目" `
|
|
48
|
+
--private `
|
|
49
|
+
--no-push
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
它会做这些事:
|
|
53
|
+
|
|
54
|
+
1. 检查 `GITEE_TOKEN`,令牌缺失或无效会直接退出。
|
|
55
|
+
2. 在 Gitee 当前 token 对应的个人账号下创建新仓库。
|
|
56
|
+
3. 把源仓库克隆到当前运行目录下的 `my-new-repo` 文件夹。
|
|
57
|
+
4. 删除克隆下来的原 `.git`。
|
|
58
|
+
5. 重新执行 `git init`。
|
|
59
|
+
6. 把 `origin` 指向新 Gitee 仓库,默认优先使用 HTTPS 地址。
|
|
60
|
+
7. 执行 `git add .` 和一次初始化提交。
|
|
61
|
+
8. 默认继续推送到新 Gitee 仓库;如果加了 `--no-push`,只在本地准备好。
|
|
62
|
+
|
|
63
|
+
如果加了 `--no-push`,最后会提示完整推送命令,例如:
|
|
64
|
+
|
|
65
|
+
```powershell
|
|
66
|
+
git -C "F:\0.代码\脚手架维护\test\my-new-repo" push -u origin HEAD
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 发布
|
|
70
|
+
|
|
71
|
+
## kill
|
|
72
|
+
|
|
73
|
+
释放本机端口:
|
|
74
|
+
|
|
75
|
+
```powershell
|
|
76
|
+
wzp kill 8080
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Windows 下会查找正在监听该 TCP 端口的进程,然后执行:
|
|
80
|
+
|
|
81
|
+
```powershell
|
|
82
|
+
taskkill /PID <进程ID> /F
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
如果没有进程监听该端口,会直接提示无需处理。
|
|
86
|
+
|
|
87
|
+
## 发布
|
|
88
|
+
|
|
89
|
+
如果这是第一次发布 `wzp` 这个新包名:
|
|
90
|
+
|
|
91
|
+
```powershell
|
|
92
|
+
npm login
|
|
93
|
+
npm publish --access=public --otp=你的6位验证码
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
后续更新:
|
|
97
|
+
|
|
98
|
+
```powershell
|
|
99
|
+
npm version patch
|
|
100
|
+
npm publish --access=public --otp=你的6位验证码
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
别人更新:
|
|
104
|
+
|
|
105
|
+
```powershell
|
|
106
|
+
npm i -g @wangzhanpeng996/wzp@latest
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## 继续扩展新功能
|
|
110
|
+
|
|
111
|
+
以后新增功能时,在 `bin/wzp.js` 里增加新的子命令分支即可,例如:
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
wzp md-init
|
|
115
|
+
wzp env-check
|
|
116
|
+
wzp config
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
推荐把 `wzp` 作为总入口,把具体能力都放在子命令里。
|
package/bin/wzp.js
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { mkdir, readdir, rm, writeFile } from "node:fs/promises";
|
|
6
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
7
|
+
import process from "node:process";
|
|
8
|
+
import readline from "node:readline/promises";
|
|
9
|
+
|
|
10
|
+
const globalHelp = `
|
|
11
|
+
用法:
|
|
12
|
+
wzp <命令> [参数]
|
|
13
|
+
|
|
14
|
+
可用命令:
|
|
15
|
+
gitee-create 从源仓库克隆项目,断开原 git 历史,创建并绑定新的 Gitee 仓库
|
|
16
|
+
create gitee-create 的兼容别名
|
|
17
|
+
kill 释放指定端口,例如 wzp kill 8080
|
|
18
|
+
kill-port kill 的兼容别名
|
|
19
|
+
|
|
20
|
+
示例:
|
|
21
|
+
wzp gitee-create https://gitee.com/wzplxt/web1.git my-new-repo
|
|
22
|
+
wzp gitee-create --source https://gitee.com/wzplxt/web1.git --repo my-new-repo
|
|
23
|
+
wzp kill 8080
|
|
24
|
+
|
|
25
|
+
查看子命令帮助:
|
|
26
|
+
wzp gitee-create --help
|
|
27
|
+
wzp kill --help
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
const giteeCreateHelp = `
|
|
31
|
+
用法:
|
|
32
|
+
wzp gitee-create <源仓库地址> <新仓库名>
|
|
33
|
+
wzp gitee-create --source <源仓库地址> --repo <新仓库名>
|
|
34
|
+
|
|
35
|
+
常用参数:
|
|
36
|
+
--source <url> 要克隆的原项目地址,也可以作为第 1 个位置参数
|
|
37
|
+
--repo <name> 新 Gitee 仓库路径名,也会作为当前目录下的新项目文件夹名
|
|
38
|
+
--description <text> Gitee 仓库简介
|
|
39
|
+
--private 创建私有 Gitee 仓库
|
|
40
|
+
--no-push 初始化提交后不推送到 origin
|
|
41
|
+
-h, --help 显示帮助
|
|
42
|
+
|
|
43
|
+
认证方式:
|
|
44
|
+
启动后会检查环境变量 GITEE_TOKEN。没有令牌或令牌无效会直接退出。
|
|
45
|
+
不要把 Gitee 密码写进命令或脚本。推送认证交给本机 git 凭据管理器。
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
const killHelp = `
|
|
49
|
+
用法:
|
|
50
|
+
wzp kill <端口号>
|
|
51
|
+
wzp kill-port <端口号>
|
|
52
|
+
|
|
53
|
+
示例:
|
|
54
|
+
wzp kill 8080
|
|
55
|
+
|
|
56
|
+
说明:
|
|
57
|
+
Windows 下会查找正在监听该 TCP 端口的进程,并执行 taskkill /F。
|
|
58
|
+
请只对你确认可以关闭的本地开发服务使用。
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
main().catch((error) => {
|
|
62
|
+
console.error(`\n失败: ${error.message}`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
async function main() {
|
|
67
|
+
const args = process.argv.slice(2);
|
|
68
|
+
const command = args[0];
|
|
69
|
+
|
|
70
|
+
if (!command || command === "--help" || command === "-h") {
|
|
71
|
+
console.log(globalHelp.trim());
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (command === "gitee-create" || command === "create") {
|
|
76
|
+
await runGiteeCreate(args.slice(1));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (command === "kill" || command === "kill-port") {
|
|
81
|
+
await runKillPort(args.slice(1));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
throw new Error(`未知命令: ${command}\n\n${globalHelp.trim()}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function runKillPort(args) {
|
|
89
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
90
|
+
console.log(killHelp.trim());
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const port = args.find((arg) => !arg.startsWith("-"));
|
|
95
|
+
if (!port || !/^\d+$/.test(port)) {
|
|
96
|
+
throw new Error("缺少端口号,例如: wzp kill 8080");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const portNumber = Number(port);
|
|
100
|
+
if (portNumber < 1 || portNumber > 65535) {
|
|
101
|
+
throw new Error("端口号必须在 1 到 65535 之间");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (process.platform === "win32") {
|
|
105
|
+
await killPortOnWindows(portNumber);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
await killPortOnUnix(portNumber);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function runGiteeCreate(args) {
|
|
113
|
+
const parsed = parseArgs(args);
|
|
114
|
+
if (parsed.help) {
|
|
115
|
+
console.log(giteeCreateHelp.trim());
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const rl = readline.createInterface({
|
|
120
|
+
input: process.stdin,
|
|
121
|
+
output: process.stdout
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const source = await requiredValue(rl, parsed, "source", "源仓库地址");
|
|
126
|
+
const repo = await requiredValue(rl, parsed, "repo", "新仓库名");
|
|
127
|
+
const authUser = await validateGiteeToken(process.env.GITEE_TOKEN);
|
|
128
|
+
console.log(`Gitee 令牌有效,当前用户: ${authUser.login || authUser.name || authUser.id}`);
|
|
129
|
+
|
|
130
|
+
const target = resolve(process.cwd(), repo);
|
|
131
|
+
const push = !parsed.options.noPush;
|
|
132
|
+
|
|
133
|
+
await ensureTargetAvailable(target);
|
|
134
|
+
|
|
135
|
+
const remote = await createGiteeRepo({
|
|
136
|
+
owner: authUser.login,
|
|
137
|
+
repo,
|
|
138
|
+
name: repo,
|
|
139
|
+
description: parsed.options.description ?? "",
|
|
140
|
+
isPrivate: Boolean(parsed.options.private),
|
|
141
|
+
token: process.env.GITEE_TOKEN
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
await run("git", ["clone", source, target]);
|
|
145
|
+
await detachGitHistory(target);
|
|
146
|
+
await run("git", ["init"], { cwd: target });
|
|
147
|
+
await run("git", ["remote", "add", "origin", remote], { cwd: target });
|
|
148
|
+
await run("git", ["add", "."], { cwd: target });
|
|
149
|
+
await run("git", ["commit", "-m", "初始化项目"], { cwd: target, allowFailure: true });
|
|
150
|
+
|
|
151
|
+
if (push) {
|
|
152
|
+
await run("git", ["push", "-u", "origin", "HEAD"], { cwd: target });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log("\n完成");
|
|
156
|
+
console.log(`本地目录: ${target}`);
|
|
157
|
+
console.log(`新 remote: ${remote}`);
|
|
158
|
+
if (!push) {
|
|
159
|
+
console.log(`尚未推送。确认无误后可执行: git -C "${target}" push -u origin HEAD`);
|
|
160
|
+
}
|
|
161
|
+
} finally {
|
|
162
|
+
rl.close();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function parseArgs(args) {
|
|
167
|
+
const result = {
|
|
168
|
+
help: args.includes("--help") || args.includes("-h"),
|
|
169
|
+
options: {}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const positionals = [];
|
|
173
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
174
|
+
const arg = args[i];
|
|
175
|
+
if (arg === "--help" || arg === "-h") {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (!arg.startsWith("--")) {
|
|
179
|
+
positionals.push(arg);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const [rawKey, inlineValue] = arg.slice(2).split("=", 2);
|
|
184
|
+
const key = toCamelCase(rawKey);
|
|
185
|
+
if (["private", "noPush"].includes(key)) {
|
|
186
|
+
result.options[key] = true;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const value = inlineValue ?? args[i + 1];
|
|
191
|
+
if (!value || value.startsWith("--")) {
|
|
192
|
+
throw new Error(`参数 --${rawKey} 缺少值`);
|
|
193
|
+
}
|
|
194
|
+
result.options[key] = value;
|
|
195
|
+
if (inlineValue === undefined) {
|
|
196
|
+
i += 1;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
result.options.source ??= positionals[0];
|
|
201
|
+
result.options.repo ??= positionals[1];
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function toCamelCase(value) {
|
|
206
|
+
return value.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function requiredValue(rl, parsed, key, label) {
|
|
210
|
+
const current = parsed.options[key];
|
|
211
|
+
if (current) {
|
|
212
|
+
return current;
|
|
213
|
+
}
|
|
214
|
+
const answer = await rl.question(`${label}: `);
|
|
215
|
+
if (!answer.trim()) {
|
|
216
|
+
throw new Error(`缺少必填参数: ${label}`);
|
|
217
|
+
}
|
|
218
|
+
return answer.trim();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function ensureTargetAvailable(target) {
|
|
222
|
+
if (!isAbsolute(target)) {
|
|
223
|
+
throw new Error("本地目标目录必须能解析为绝对路径");
|
|
224
|
+
}
|
|
225
|
+
if (!existsSync(dirname(target))) {
|
|
226
|
+
await mkdir(dirname(target), { recursive: true });
|
|
227
|
+
}
|
|
228
|
+
if (!existsSync(target)) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const entries = await readdir(target);
|
|
232
|
+
if (entries.length > 0) {
|
|
233
|
+
throw new Error(`目标目录已存在且不是空目录: ${target}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function detachGitHistory(target) {
|
|
238
|
+
const gitDir = join(target, ".git");
|
|
239
|
+
const resolvedGitDir = resolve(gitDir);
|
|
240
|
+
const resolvedTarget = resolve(target);
|
|
241
|
+
if (!resolvedGitDir.startsWith(`${resolvedTarget}\\`) && resolvedGitDir !== join(resolvedTarget, ".git")) {
|
|
242
|
+
throw new Error(`拒绝删除异常路径: ${resolvedGitDir}`);
|
|
243
|
+
}
|
|
244
|
+
if (existsSync(gitDir)) {
|
|
245
|
+
await rm(gitDir, { recursive: true, force: true });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function createGiteeRepo({ owner, repo, name, description, isPrivate, token }) {
|
|
250
|
+
const body = new URLSearchParams({
|
|
251
|
+
access_token: token,
|
|
252
|
+
name,
|
|
253
|
+
path: repo,
|
|
254
|
+
description,
|
|
255
|
+
private: String(isPrivate)
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const response = await fetch("https://gitee.com/api/v5/user/repos", {
|
|
259
|
+
method: "POST",
|
|
260
|
+
headers: {
|
|
261
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
262
|
+
},
|
|
263
|
+
body
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const payload = await response.json().catch(() => ({}));
|
|
267
|
+
if (!response.ok) {
|
|
268
|
+
const message = payload.message || payload.error || response.statusText;
|
|
269
|
+
throw new Error(`Gitee 建仓失败: ${message}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return payload.html_url || payload.ssh_url || `https://gitee.com/${owner}/${repo}.git`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function validateGiteeToken(token) {
|
|
276
|
+
if (!token) {
|
|
277
|
+
throw new Error("缺少 GITEE_TOKEN。请先在当前 PowerShell 会话执行: $env:GITEE_TOKEN=\"你的Gitee访问令牌\"");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const url = new URL("https://gitee.com/api/v5/user");
|
|
281
|
+
url.searchParams.set("access_token", token);
|
|
282
|
+
const response = await fetch(url);
|
|
283
|
+
const payload = await response.json().catch(() => ({}));
|
|
284
|
+
|
|
285
|
+
if (!response.ok || !payload.id) {
|
|
286
|
+
const message = payload.message || payload.error || response.statusText;
|
|
287
|
+
throw new Error(`GITEE_TOKEN 无效或权限不足: ${message}`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return payload;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function run(command, args, options = {}) {
|
|
294
|
+
return new Promise((resolvePromise, reject) => {
|
|
295
|
+
console.log(`> ${command} ${args.join(" ")}`);
|
|
296
|
+
const child = spawn(command, args, {
|
|
297
|
+
cwd: options.cwd ?? process.cwd(),
|
|
298
|
+
stdio: "inherit",
|
|
299
|
+
shell: false
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
child.on("error", reject);
|
|
303
|
+
child.on("exit", (code) => {
|
|
304
|
+
if (code === 0 || options.allowFailure) {
|
|
305
|
+
resolvePromise();
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
reject(new Error(`命令退出码 ${code}: ${command} ${args.join(" ")}`));
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function killPortOnWindows(port) {
|
|
314
|
+
const output = await capture("netstat", ["-ano", "-p", "TCP"]);
|
|
315
|
+
const pids = new Set();
|
|
316
|
+
|
|
317
|
+
for (const line of output.split(/\r?\n/)) {
|
|
318
|
+
const trimmed = line.trim();
|
|
319
|
+
if (!trimmed || !trimmed.includes("LISTENING")) {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const parts = trimmed.split(/\s+/);
|
|
324
|
+
const localAddress = parts[1];
|
|
325
|
+
const state = parts[3];
|
|
326
|
+
const pid = parts[4];
|
|
327
|
+
if (state !== "LISTENING" || !pid) {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (addressUsesPort(localAddress, port)) {
|
|
332
|
+
pids.add(pid);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (pids.size === 0) {
|
|
337
|
+
console.log(`端口 ${port} 没有监听进程。`);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
for (const pid of pids) {
|
|
342
|
+
console.log(`释放端口 ${port}: 结束 PID ${pid}`);
|
|
343
|
+
await run("taskkill", ["/PID", pid, "/F"]);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
console.log(`端口 ${port} 已释放。`);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async function killPortOnUnix(port) {
|
|
350
|
+
const output = await capture("lsof", ["-ti", `tcp:${port}`, "-sTCP:LISTEN"], { allowFailure: true });
|
|
351
|
+
const pids = output
|
|
352
|
+
.split(/\r?\n/)
|
|
353
|
+
.map((line) => line.trim())
|
|
354
|
+
.filter(Boolean);
|
|
355
|
+
|
|
356
|
+
if (pids.length === 0) {
|
|
357
|
+
console.log(`端口 ${port} 没有监听进程。`);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
for (const pid of pids) {
|
|
362
|
+
console.log(`释放端口 ${port}: 结束 PID ${pid}`);
|
|
363
|
+
await run("kill", ["-9", pid]);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
console.log(`端口 ${port} 已释放。`);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function addressUsesPort(address, port) {
|
|
370
|
+
if (!address) {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
if (address.startsWith("[") && address.includes("]:")) {
|
|
374
|
+
return address.endsWith(`]:${port}`);
|
|
375
|
+
}
|
|
376
|
+
return address.endsWith(`:${port}`);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function capture(command, args, options = {}) {
|
|
380
|
+
return new Promise((resolvePromise, reject) => {
|
|
381
|
+
const child = spawn(command, args, {
|
|
382
|
+
cwd: options.cwd ?? process.cwd(),
|
|
383
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
384
|
+
shell: false
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
let stdout = "";
|
|
388
|
+
let stderr = "";
|
|
389
|
+
child.stdout.on("data", (chunk) => {
|
|
390
|
+
stdout += chunk.toString();
|
|
391
|
+
});
|
|
392
|
+
child.stderr.on("data", (chunk) => {
|
|
393
|
+
stderr += chunk.toString();
|
|
394
|
+
});
|
|
395
|
+
child.on("error", reject);
|
|
396
|
+
child.on("exit", (code) => {
|
|
397
|
+
if (code === 0 || options.allowFailure) {
|
|
398
|
+
resolvePromise(stdout);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
reject(new Error(`命令退出码 ${code}: ${command} ${args.join(" ")}\n${stderr.trim()}`));
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wangzhanpeng996/wzp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Personal CLI toolbox for Gitee project creation and other WZP workflows.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"wzp": "bin/wzp.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"start": "node ./bin/wzp.js",
|
|
19
|
+
"check": "node ./bin/wzp.js --help"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"gitee",
|
|
23
|
+
"git",
|
|
24
|
+
"clone",
|
|
25
|
+
"cli",
|
|
26
|
+
"toolbox"
|
|
27
|
+
],
|
|
28
|
+
"license": "MIT"
|
|
29
|
+
}
|