Skip to main content
On this page

构建 CLI 应用

Deno 是发布命令行工具的绝佳方式。你的工具只是 TypeScript,因此 运行它不需要构建步骤。准备分发时,deno compile 会将其转换为单个自包含的可执行文件, 在未安装 Deno 的情况下也能运行。

读取命令行参数 Jump to heading

原始参数可通过 Deno.args 获取。对于实际工具, 请使用 @std/cli 进行解析,它可以处理标志、选项和默认值:

greet.ts
import { parseArgs } from "jsr:@std/cli/parse-args";

const flags = parseArgs(Deno.args, {
  string: ["name"],
  default: { name: "world" },
});

console.log(`Hello, ${flags.name}!`);
>_
$ deno run greet.ts --name=Deno
Hello, Deno!

从 stdin 读取并检测 TTY Jump to heading

好的 CLI 工具可以与管道组合使用。Deno.stdin 将标准输入暴露为符合 Web 标准的 ReadableStream,因此读取所有 管道输入并将其作为文本处理的最简单方法是将其包装在 Response 中:

const input = await new Response(Deno.stdin.readable).text();

当你的工具两种方式都可用时,请使用 Deno.stdin.isTerminal() 来判断 stdin 是交互式终端还是管道,并据此分支处理。这也是工具决定是否输出颜色和进度条(交互式) 或纯粹的机器可读输出(管道)的方式。这里,当没有管道输入时,工具会回退为向用户询问:

count.ts
let input: string;

if (Deno.stdin.isTerminal()) {
  input = prompt("输入一些文本:") ?? "";
} else {
  input = await new Response(Deno.stdin.readable).text();
}

console.log(`获取到 ${input.trim().length} 个字符`);
>_
$ echo "hello from a pipe" | deno run count.ts
获取到 17 个字符
$ deno run count.ts
输入一些文本: hi
获取到 2 个字符

读取 stdin 不需要任何权限标志。

如果你正在移植 Node.js CLI,或者使用会读取输入的 npm 库, Node 风格的 process.stdin(来自 node:processprocess 全局对象)在 Deno 中也同样可用, 因此那段代码无需修改即可运行。

提示用户 Jump to heading

对于简单的交互式输入,Deno 提供了浏览器全局函数 prompt()confirm()alert()。它们会阻塞直到用户回应(执行会暂停在那一行,直到输入到来前不会运行其他内容), 并且不需要导入或权限:

setup.ts
const name = prompt("项目名称:", "my-app");
const ok = confirm(`创建 ${name}`);
if (!ok) Deno.exit(1);
console.log(`正在创建 ${name}...`);

如果需要更高级的功能,请使用 @std/cli。除了 参数解析之外,它还包含旋转加载器、进度条和选择提示。这些内容位于以 unstable- 为前缀的模块中,因此它们的 API 可能仍会变化。Spinner 类会在你的工具运行时显示一条动画状态行:

spin.ts
import { Spinner } from "jsr:@std/cli/unstable-spinner";

const spinner = new Spinner({ message: "正在下载..." });
spinner.start();
await new Promise((resolve) => setTimeout(resolve, 2000));
spinner.stop();
console.log("完成");
>_
deno run spin.ts

使用正确的代码退出 Jump to heading

Shell、脚本和 CI 系统都依赖你的工具的退出码:零表示 成功,其他任何值都表示失败。Deno.exit() 会立即以给定代码终止进程:

if (Deno.args.length === 0) {
  console.error("用法:check <file>...");
  Deno.exit(2);
}

由于 Deno.exit() 会立即停止进程, 像刷新日志或 finally 块之类的待处理工作都不会运行。当你想要 记录失败但继续执行时,请改用 Deno.exitCode。进程会继续运行, 自然结束,然后以你设置的代码退出。这是适用于 linter 和批处理工具的正确模式,它们应该报告每个问题,而不是 在第一个问题处停止:

check.ts
for (const file of Deno.args) {
  try {
    await Deno.lstat(file);
  } catch {
    console.error(`缺失:${file}`);
    Deno.exitCode = 1;
  }
}
console.log("检查完成");
>_
$ deno run --allow-read check.ts real.txt nope.txt
缺失:nope.txt
检查完成
$ echo $?
1

对于非零代码,除“失败”之外没有固定含义,但常见的 约定是:1 表示你的工具检查出的错误,2 表示 诸如缺少参数之类的用法错误。

编译为单个可执行文件 Jump to heading

deno compile 会将你的程序和 Deno 运行时打包成一个二进制文件:没有 node_modules,用户也无需安装步骤。

>_
deno compile greet.ts
./greet --name=Deno

使用 --output 为二进制文件命名。如果你的工具需要权限,请使用常规的 --allow-* 标志 将其预先写入,这样运行时就不会再提示:

>_
deno compile --output greet greet.ts

将资源嵌入二进制文件 Jump to heading

即使你的工具需要模板、单词列表或默认配置等数据,单文件可执行程序也应保持单文件。 传入 --include 可将文件或目录打包进二进制文件:

>_
deno compile --include names.csv --output greet greet.ts

运行时,通过 import.meta.dirname 按相对于模块目录的路径读取嵌入文件:

const names = Deno.readTextFileSync(import.meta.dirname + "/names.csv");

同样的代码在开发过程中使用 deno run 以及在编译后的 二进制文件中都能正常工作,不受用户从哪里运行它的影响。关于目录包含以及如何在 deno.json 中 一次性配置路径,请参见 包含数据文件

为其他平台交叉编译 Jump to heading

使用 --target 为不同于你当前平台的平台构建,这样你就可以从一台机器 为 Windows、macOS 和 Linux 分发二进制文件:

>_
deno compile --target x86_64-pc-windows-msvc --output greet.exe greet.ts
deno compile --target aarch64-apple-darwin --output greet greet.ts

完整的目标列表覆盖了 Linux、macOS 和 Windows 的 x86_64 与 ARM 构建;请参见 支持的目标。如果你将二进制文件 分发给最终用户,也可以对其进行签名,以便操作系统信任你的工具;关于 macOS 和 Windows 的步骤,请参见 代码签名

从源代码安装工具 Jump to heading

要在你自己的机器上将脚本作为命令使用(无需编译), 可以使用 deno install 将其全局安装:

>_
deno install --global --name greet greet.ts
greet --name=Deno

进一步了解 Jump to heading

  • deno compile 所有目标、标志、资源 嵌入、图标和代码签名。
  • deno install 安装工具和 依赖项。
  • @std/cli 参数解析、旋转加载器、进度 条和交互式提示。
  • Deno API reference Deno 命名空间上的所有内容, 包括 stdin、退出码和文件 API。
  • Permissions 精确选择你的 编译后工具可以访问的内容。

Did you find what you needed?

编辑此页面
Privacy policy