【bun】探索使用bun实现一个命令行工具
思考
在我们使用有些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()
}
修改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上面
略
为之则易,不为则难。

浙公网安备 33010602011771号