astro bun Deno logo
Skip to content
Go back

Bun实现一个命令行工具

Edit page

思考

在我们使用有些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();
}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上面


Edit page
Share this post on:

Next Post
Ts实现一个模式匹配