On this page
构建 CLI 应用
Deno 是发布命令行工具的绝佳方式。你的工具只是 TypeScript,因此
运行它不需要构建步骤。准备分发时,deno compile 会将其转换为单个自包含的可执行文件,
在未安装 Deno 的情况下也能运行。
读取命令行参数 Jump to heading
原始参数可通过 Deno.args 获取。对于实际工具,
请使用 @std/cli 进行解析,它可以处理标志、选项和默认值:
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 是交互式终端还是管道,并据此分支处理。这也是工具决定是否输出颜色和进度条(交互式)
或纯粹的机器可读输出(管道)的方式。这里,当没有管道输入时,工具会回退为向用户询问:
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:process 或 process 全局对象)在 Deno 中也同样可用,
因此那段代码无需修改即可运行。
提示用户 Jump to heading
对于简单的交互式输入,Deno 提供了浏览器全局函数 prompt()、
confirm() 和 alert()。它们会阻塞直到用户回应(执行会暂停在那一行,直到输入到来前不会运行其他内容),
并且不需要导入或权限:
const name = prompt("项目名称:", "my-app");
const ok = confirm(`创建 ${name}?`);
if (!ok) Deno.exit(1);
console.log(`正在创建 ${name}...`);
如果需要更高级的功能,请使用 @std/cli。除了
参数解析之外,它还包含旋转加载器、进度条和选择提示。这些内容位于以 unstable-
为前缀的模块中,因此它们的 API 可能仍会变化。Spinner 类会在你的工具运行时显示一条动画状态行:
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 和批处理工具的正确模式,它们应该报告每个问题,而不是
在第一个问题处停止:
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。 精确选择你的 编译后工具可以访问的内容。