【bun】探索使用bun实现一个命令行工具

思考

在我们使用有些npm库(cli)时,我们可以使用某些指令(命令行接口)来使用该库。例如rolluprollup src/main.js -f cjs;或vitevite dev

那么他们时如何实现的?

关于node是实现该功能网上有很多文章,大家可自行查阅。

接下来我们使用bun来实现该功能。其实与nodejs实现原理一致。
我们来实现一个简单的关闭端口占用的工具;

创建项目

  1. 新建kill-port-bun文件夹
  2. 使用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上面

posted @ 2025-02-08 18:44  demo_you  阅读(186)  评论(0)    收藏  举报