思考
在我们使用有些npm库(cli)时,我们可以使用某些指令(命令行接口)来使用该库。例如rollup的rollup src/main.js -f cjs;或vite的vite dev。
那么他们时如何实现的?
关于node是实现该功能网上有很多文章,大家可自行查阅。
接下来我们使用bun来实现该功能。其实与nodejs实现原理一致。
我们来实现一个简单的关闭端口占用的工具;
创建项目
- 新建
kill-port-bun文件夹 - 使用
bun init来初始化项目。
index.ts
#!/usr/bin/env bun
import packageJson from "./package.json";
const version = packageJson.version;
const platform = process.platform;
const exec: Partial<
Record<
typeof platform,
(port: string | number, silent?: boolean) => Promise<void>
>
> = {
darwin: async (port: string | number, silent = false) => {
const portStr = String(port);
const result = await Bun.$`lsof -ti :${portStr}`.nothrow().quiet();
// lsof returns non-zero exit code when no process is found
if (result.exitCode !== 0) {
if (!silent) console.log(`No process found using port ${portStr}`);
return;
}
const pids = result
.text()
.trim()
.split(/[\n\r]/)
.filter(Boolean);
if (pids.length === 0) {
if (!silent) console.log(`No process found using port ${portStr}`);
return;
}
for (const pid of pids) {
if (!silent) console.log(`Killing process ${pid} on port ${portStr}`);
const killResult = await Bun.$`kill -9 ${pid}`.nothrow().quiet();
if (killResult.exitCode !== 0) {
throw new Error(
`Failed to kill process ${pid}: ${killResult.stderr.toString().trim()}`
);
}
}
},
linux: async (port: string | number, silent = false) => {
const portStr = String(port);
const result = await Bun.$`lsof -ti :${portStr}`.nothrow().quiet();
// lsof returns non-zero exit code when no process is found
if (result.exitCode !== 0) {
if (!silent) console.log(`No process found using port ${portStr}`);
return;
}
const pids = result
.text()
.trim()
.split(/[\n\r]/)
.filter(Boolean);
if (pids.length === 0) {
if (!silent) console.log(`No process found using port ${portStr}`);
return;
}
for (const pid of pids) {
if (!silent) console.log(`Killing process ${pid} on port ${portStr}`);
const killResult = await Bun.$`kill -9 ${pid}`.nothrow().quiet();
if (killResult.exitCode !== 0) {
throw new Error(
`Failed to kill process ${pid}: ${killResult.stderr.toString().trim()}`
);
}
}
},
win32: async (port: string | number, silent = false) => {
const portStr = String(port);
const result = await Bun.$`netstat -ano|findstr ${portStr}`
.nothrow()
.quiet();
// findstr returns non-zero exit code when no match is found
if (result.exitCode !== 0) {
if (!silent) console.log(`No process found using port ${portStr}`);
return;
}
const lines = result
.text()
.trim()
.split(/[\n\r]/)
.filter(Boolean);
const pids = [...new Set(lines.map(line => line.match(/\d+$/)?.at(0)))];
if (pids.length === 0) {
if (!silent) console.log(`No process found using port ${portStr}`);
return;
}
for (const pid of pids) {
if (!silent) console.log(`Killing process ${pid} on port ${portStr}`);
const killResult = await Bun.$`taskkill /pid ${pid} -t -f`
.nothrow()
.quiet();
if (killResult.exitCode !== 0) {
throw new Error(
`Failed to kill process ${pid}: ${killResult.stderr.toString().trim()}`
);
}
}
},
// TODO: Add support for other platforms
};
/**
* Kill process(es) using the specified port
* @param port - The port number to kill processes on
* @param options - Optional configuration
* @param options.silent - If true, suppresses console output (default: false)
* @returns Promise that resolves when process(es) are killed
* @throws Error if the platform is not supported
*
* @example
* ```ts
* import { kill } from 'kill-port-bun'
*
* // Kill process on port 3000
* await kill(3000)
*
* // Kill process silently
* await kill(8080, { silent: true })
* ```
*/
export async function kill(
port: string | number,
options?: { silent?: boolean }
): Promise<void> {
const silent = options?.silent ?? false;
const run = exec[platform];
if (!run) {
throw new Error(`Unsupported platform: ${platform}`);
}
await run(port, silent);
}
// Legacy function name for backward compatibility
const portKill = async (port: string) => {
await kill(port, { silent: false });
};
function logHelp(): void {
console.log(`
Port Killer v${version}
Usage:
kp <port> Kill process using the port
kp --version Show version number
Examples:
kp 8080
`);
}
function formatArgv() {
const argv = Bun.argv.slice(2);
if (argv.length === 0) {
logHelp();
return;
}
const arg = argv.at(0)?.trim();
if (!arg) {
logHelp();
return;
}
if (arg === "--version") {
console.log(`Port Killer v${version}`);
return;
}
// If it's a number, treat it as a port
if (!isNaN(Number(arg))) {
portKill(arg);
return;
}
console.error(`Invalid port: ${arg}`);
}
// Only run CLI logic if this file is the main entry point (not imported as a library)
if (import.meta.main) {
formatArgv();
}index.ts
修改package.json
{
"name": "kill-port-bun",
"module": "index.ts",
"version": "1.0.0",
"type": "module",
"devDependencies": {
"@types/bun": "latest"
},
"scripts": {
"start": "bun index.ts"
},
"bin": {
"printer": "index.ts"
},
"peerDependencies": {
"typescript": "^5.0.0"
}
}
安装到当前项目
bun install <PATH/kill-port-bun>
就可以使用啦
作为CLI使用
bun run kp 3000
Killing process 43040 on port 3000
BUN项目内使用
import { kill } from "kill-port-bun";
kill(3000);
全局使用
bun install -g .
也可发布到npm上面
略