# AfterPack CLI 参考

调用 npx afterpack@latest：可选的路径参数、作为标志使用的每个配置键、verify、restore 与 audit 子命令，以及完整的退出码约定。

Source: https://www.afterpack.dev/zh/docs/cli

`npx afterpack@latest` 混淆一个构建目录。当你的打包工具没有对应插件时，或者在 [CI](https://www.afterpack.dev/zh/docs/builds) 中，使用它。如果你的框架有插件，请改为安装 `@afterpack/<fw>`：这样常规构建会直接产出已混淆的输出，不需要单独的步骤。参见[框架](https://www.afterpack.dev/zh/docs/frameworks)。

本页涵盖[参数](#usage)、[示例](#examples)、[一次运行会做什么](#what-a-run-does)、[`verify`](#afterpack-verify-dir)、[`restore`](#afterpack-restore-dir)、[`audit`](#afterpack-audit-url)、[机器可读输出](#machine-readable-output)以及[退出码](#exit-codes)。每个选项都只在[配置](https://www.afterpack.dev/zh/docs/config)页定义一次。

## 用法

```
afterpack [path] [--<key>=<value> ...] [-h | --help] [-v | --version]
afterpack verify [dir]
afterpack restore [dir]
afterpack audit <url>
```

```bash
npx afterpack@latest
```

路径参数可以是以下两种之一：

- 一个目录，递归遍历其中的每个 `.js`、`.mjs` 和 `.cjs` 文件；或者
- 单个 `.js`、`.mjs` 或 `.cjs` 文件，单独混淆。

不带路径运行时，`afterpack` 会检测你的构建输出：先看 `package.json` 中声明的打包工具按惯例写入的目录（Next.js → `.next/`，Nuxt → `.output/`，SvelteKit → `build/`，Electron → `out/`，其他受支持的打包工具均为 `dist/`），该目录存在就使用它；否则在 `dist/`、`build/`、`out/`、`.output/` 和 `.next/` 中选取最新的一个。它从不读取打包工具自己的配置文件，所以自定义的输出目录不会被检测到，这种情况请自行传入路径。它会打印找到了什么、将要做什么，然后开始运行；在 CI 中它从不提示交互。什么都没找到时，它会打印快速上手帮助并以 `1` 退出。该命令还会从 `package.json` 读取你的打包工具，并在运行结束时打印一行提示，指向[对应的插件](https://www.afterpack.dev/zh/docs/frameworks)。

请指向打包工具的输出目录，而不是源代码。大多数打包工具（[Vite](https://www.afterpack.dev/zh/docs/frameworks/vite)、[Rollup](https://www.afterpack.dev/zh/docs/frameworks/rollup)、[esbuild](https://www.afterpack.dev/zh/docs/frameworks/esbuild)、[webpack](https://www.afterpack.dev/zh/docs/frameworks/webpack)、[Astro](https://www.afterpack.dev/zh/docs/frameworks/astro)、[Parcel](https://www.afterpack.dev/zh/docs/frameworks/parcel)）写入 `dist/`。[Next.js](https://www.afterpack.dev/zh/docs/frameworks/nextjs) 写入 `.next/`，[Nuxt](https://www.afterpack.dev/zh/docs/frameworks/nuxt) 写入 `.output/`。

## 示例

```bash
npx afterpack@latest                                        # detects the build output, light default
npx afterpack@latest dist/                                  # a directory you choose
npx afterpack@latest dist/ --preset=hard                    # heavier posture
npx afterpack@latest .next/ --seed=git                      # reproducible output, pinned to the commit
npx afterpack@latest build/widget.js                        # one file
npx afterpack@latest dist/ --diagnostics.level=all          # every diagnostic, not the rolled-up summary
AFTERPACK_complexity=40 npx afterpack@latest dist/           # any flag also works as an env var
```

## 一次运行会做什么

1. 收集 `<path>` 下的 JavaScript。路径是目录时递归处理，跳过它自己留下的 [`.backup.<hash>` 副本](#files)，并跳过 `node_modules/`，除非你用 [`--paths.include='**/node_modules/**'`](https://www.afterpack.dev/zh/docs/config#paths-include) 显式包含它。什么都没收集到时，构建失败。
2. 通过 `.afterpack/` 内的一个 `.gitignore` 让该目录不进入 git，这个文件在首次需要时写入。你自己的 `.gitignore` 从不会被修改。
3. 按请求的[预设](https://www.afterpack.dev/zh/docs/config#preset)就地混淆每个收集到的文件；如果你显式设置了 [`--complexity`](https://www.afterpack.dev/zh/docs/config#complexity)，则按它执行。在 Next.js、Angular、Vite 或 webpack 构建上，它还会把每个内容哈希文件重命名为与其混淆后字节相符的名称，并改写对它的每一处引用；在其他输出上，文件名保持打包工具写下的样子。参见[内容哈希文件名](#content-hashed-file-names)。
4. 写出标志所允许的产物，并且总会写出[保护回执](#files)。开启时（默认在能找到 source map 且构建不在 CI 中运行时开启），合并后的 [Protection Map](https://www.afterpack.dev/zh/docs/protection-map) 落在 `.afterpack/protectionMap.html`，已加入 gitignore 且从不对外提供，因为它内嵌了你的原始源代码。source map 或备份同样包含你的完整源代码。两者都不要部署。若其中一个落在会对外提供的路径段之下，CLI 会警告一次。
5. 打印[诊断信息](https://www.afterpack.dev/zh/docs/diagnostics)；如果识别出了打包工具，再打印那行插件提示。

> **运行不是幂等的**
>
> 该命令会就地覆盖你的构建输出，并且不保留可供再次混淆的副本。先构建，再运行一次。在 [CI](https://www.afterpack.dev/zh/docs/builds) 中这个顺序是自动保证的。

### 不要对同一份输出运行两次

AfterPack 拒绝混淆已经带有上一次运行所产生的混淆输出的文件：它按内容哈希将收集到的文件与[保护回执](#afterpack-verify-dir)比对，并以 `DIAG_ALREADY_OBFUSCATED` 让构建失败（无论你从哪个目录运行），而不是对已混淆的代码再混淆一遍。框架对受保护输出所做的副本（例如 SvelteKit 的 `build/`）也会以同样的方式被拒绝。请先从源代码重新构建：删除输出目录，或运行打包工具的清理步骤。

### 内容哈希文件名

打包工具按 chunk 内容的哈希为它命名，例如 `index-4f9a2c1e.js`，这样服务器就能用 `Cache-Control: immutable` 把它缓存一年。混淆会改变字节，因此在下列构建上，运行会给每个内容哈希文件的名称换上新的哈希，并改写 HTML、清单和其他 chunk 中对它的每一处引用。一个文件名永远不会指向不同的字节，因此浏览器、CDN 或 service worker 永远不会在复用的名称下提供过期副本。新哈希由原哈希加上种子、引擎版本和你的设置得出，与[插件](https://www.afterpack.dev/zh/docs/frameworks)为文件命名的方式相同；[文件名与缓存](https://www.afterpack.dev/zh/docs/builds#file-names-and-caching)说明了种子对缓存意味着什么。

| 构建 | CLI 指向 | 运行会重命名的内容 |
|---|---|---|
| [Next.js](https://www.afterpack.dev/zh/docs/frameworks/nextjs)，基于 webpack 或 Turbopack | `.next/`、`.next/static/` 或 `.next/static/chunks/`；静态导出时为 `out/` | `static/chunks/` 下的 chunk |
| [Angular](https://www.afterpack.dev/zh/docs/frameworks/angular) application builder | `dist/<app>/browser/` 或 `dist/<app>/` | 每个带哈希的文件。运行还会重新计算 `integrity` 属性和 service worker 的 `ngsw.json` 哈希表 |
| [Vite](https://www.afterpack.dev/zh/docs/frameworks/vite) | 含有 `index.html` 或 `.vite/manifest.json` 的目录 | `assets/` 中，或清单所列文件夹中的 `name-<hash>` 脚本和样式表 |
| [webpack](https://www.afterpack.dev/zh/docs/frameworks/webpack) | 由 webpack 运行时加载 chunk 的目录 | 名称中带有十六进制内容哈希的文件 |

在以下情况下，内容哈希文件名保持打包工具写下的样子，运行会打印一条提示：

- 路径是单个文件，或者输出来自其他任何打包工具；
- 无法识别 Next.js 构建所用的打包工具；
- 在 Vite 或 webpack 构建上，某个带哈希的文件没有在构建目录内被引用，两个哈希无法区分，或者构建目录旁的某个文件夹（例如服务端 bundle、`.svelte-kit/` 或 `.output/`）引用了这些带哈希的文件，或大到无法检查；
- 某个预缓存清单（例如 Workbox 的）按 revision 列出这些文件。

要在这样的构建上使用长期缓存，请使用你所用框架的[插件](https://www.afterpack.dev/zh/docs/frameworks)，或固定一个不变的 [`--seed`](https://www.afterpack.dev/zh/docs/config#seed)。

如果 Angular 服务端构建的 `angular-app-manifest.mjs` 内嵌了资源哈希，运行会以退出码 `1` 拒绝，输出保持构建器写下的样子。请对不带 SSR 的构建的浏览器输出进行混淆。

## 标志

每个配置键都是一个标志：`--<key>=<value>`，例如 `--complexity=40`，列表值则写成 `--identifiers.reserved=Hls,Foo`。布尔标志单独出现表示 `true`（`--build.backup`），加上 `=false` 则关闭（`--protectionMap.enabled=false`）。`-h, --help` 和 `-v, --version` 打印后以 `0` 退出。

[选项参考](https://www.afterpack.dev/zh/docs/config)中的每个标量或列表键都可以这样作为标志使用。结构化的值应放在 `afterpack.json` 中。环境变量沿用相同的名称，只是把点换成下划线，参见[配置](https://www.afterpack.dev/zh/docs/config)，例如 [`AFTERPACK_key`](https://www.afterpack.dev/zh/docs/config#key)、[`AFTERPACK_preset`](https://www.afterpack.dev/zh/docs/config#preset) 和 [`AFTERPACK_telemetry_enabled`](https://www.afterpack.dev/zh/docs/config#telemetry-enabled)。

## 文件

| 路径 | 作用 |
|---|---|
| `afterpack.json` | 配置文件。AfterPack 从工作目录向上查找，取最近的一个匹配。它承载完整的[引擎配置](https://www.afterpack.dev/zh/docs/config#example-afterpackjson)：构建选项、[`key`](https://www.afterpack.dev/zh/docs/config#key)，一切都在这里。 |
| `.afterpack/receipts/<build dir>.json` | 某个构建目录的保护回执，例如 `.afterpack/receipts/dist.json` 或 `.afterpack/receipts/.next.json`。路径过长或不寻常时，会改用带哈希的缩短文件名。它记录每个输出文件及其写入字节的哈希、引擎版本和种子。[`afterpack verify`](#afterpack-verify-dir) 据此重新计算哈希并比对，而之后对同一份未重新构建的输出再运行一次，也正是因为它而被拒绝。 |
| `.afterpack/protectionMap.html` | 合并后的 [Protection Map](https://www.afterpack.dev/zh/docs/protection-map)。自动加入 gitignore。它内嵌了你的原始源代码。绝不要提交或对外提供它。 |
| `.afterpack/backups/<build dir>/` | 某个构建目录的原始文件，逐字节保留，例如 `.afterpack/backups/dist/`，并附带一份清单，记录本次运行创建、修改、重命名或删除的每个文件。每个构建目录都有自己的备份和自己的[锁](#parallel-runs-and-locks)，因此同一仓库中不同应用的构建从不共用备份。每次运行都会写入（[`--build.backup=false`](https://www.afterpack.dev/zh/docs/config#build-backup) 可关闭），[`afterpack restore`](#afterpack-restore-dir) 读取它来撤销该次运行。运行结束时会打印它的路径。它就是你的源代码。绝不要提交或部署它。 |
| `<file>.js.map` | 开启 [`sourceMap.enabled`](https://www.afterpack.dev/zh/docs/config#sourceMap-enabled) 时生成的 source map。默认关闭：除非声明为开发构建，否则每次运行都是[生产模式](https://www.afterpack.dev/zh/docs/config#build-mode)，而生产模式的运行还会删除打包工具写出的 source map，包括 CSS map。随包发布的 source map 会把输出直接映射回你的源代码。 |

`.afterpack/` 存放回执、Protection Map 和各个备份。AfterPack 从构建目录出发查找它，与你从哪个目录运行无关。它向上查找最近的、包含锁文件（`package-lock.json`、`npm-shrinkwrap.json`、`pnpm-lock.yaml`、`yarn.lock`、`bun.lock` 或 `bun.lockb`）、`afterpack.json` 或 `.git` 的文件夹。找不到时，使用最近的含有 `package.json` 的文件夹；仍找不到时，放在构建目录旁边。在这些文档页面中，这个文件夹就是你的项目根目录。`.afterpack/` 绝不会位于构建输出之内。

在以下情况下，运行会失败，且不会重写任何文件：在构建输出中发现了 `.afterpack/` 文件夹（`DIAG_AFTERPACK_DIR_IN_OUTPUT`：删除该文件夹后重新构建），或者无法写入回执（`DIAG_RECEIPT_UNWRITABLE`）。旧版本遗留在输出中的 `.afterpack-protection.json` 会在下一次运行时被删除。

## `afterpack verify [dir]`

部署闸门。每次受保护的构建都会把一份保护回执写入 [`.afterpack/receipts/`](#files)。`verify` 用与那次运行相同的方式为 `[dir]` 找到回执，因此可以在任何目录下运行。它对回执中列出的每个文件重新计算哈希，只要有一个不再匹配、回执缺失，或者回执来自另一次构建，就会失败。

```bash
npx afterpack@latest verify .        # a project root: looks in ./ then ./.next/
npx afterpack@latest verify dist     # or the build output directory itself
npx afterpack@latest verify build    # SvelteKit's adapter copy; also .output/public, out/
```

框架从构建输出复制出来的目录，例如 SvelteKit 的 `build/`、Nuxt 的 `.output/public/` 或 Next 的 `out/`，会按被复制目录的回执来检查。`verify` 依据其中的文件把副本与那份回执对应起来，并检查每一个哈希，因此被改动的文件会导致失败并被点名。请把它指向部署根目录本身。此时文本输出会附加 `· copy of <source dir>/`。

它不读取任何引擎配置，只读取 [`--diagnostics.format`](#machine-readable-output) 和 `--diagnostics.level`。使用 `--diagnostics.format=json` 时，摘要中的 `receipt` 字段给出它检查的回执，`copiedFrom` 字段给出副本所复制的目录。把它作为部署前的最后一步运行；参见[构建与 CI](https://www.afterpack.dev/zh/docs/builds)。

如果中途停止的运行或恢复在构建目录中留下了临时的 `.afterpack-tmp` 文件，它也会失败。参见[中途停止的运行](#a-run-that-stopped-partway)。

## `afterpack restore [dir]`

撤销操作。一次运行在写入任何内容之前，会先把你的原始文件复制到 `.afterpack/backups/<build dir>/`，并记录它将创建、修改、重命名或删除的每个文件。`restore` 把每个文件以原始名称和原始字节放回原处，撤销重命名和被改写的引用，并删除备份以及该次运行写下的回执。

```bash
npx afterpack@latest restore          # every backup under the project root
npx afterpack@latest restore dist     # only the run over dist/
```

指向某个构建目录时，它只恢复针对该目录、其内部某个目录或包含它的那个构建的运行。指向项目根目录时，它恢复那里的所有备份。如果没有任何备份覆盖该路径，它会失败，并列出拥有备份的目录。它以与运行相同的方式从 `[dir]` 找到 `.afterpack/`，也会读取早期版本写下的 `.afterpack/backup/` 文件夹。

它失败即中止：在写入任何内容*之前*，每个记录在案的文件都会被检查，因此运行之后被你编辑或重新构建过的文件会让恢复停止，什么都不会恢复，命令会列出该文件并以 `1` 退出。没有备份时它会直接拒绝。中途停止的恢复会由下一次 `restore` 完成。除 [`--diagnostics.format`](#machine-readable-output) 外不读取任何引擎配置。

### 中途停止的运行

因错误而失败的运行会在退出前回滚它自己的写入。每个文件都先写入临时的 `<file>.afterpack-tmp`，再重命名到位，因此被强行终止的运行永远不会留下写了一半的文件。被终止的运行的备份会保持“未完成”标记，同一目录上的下一次 `afterpack` 运行会先检查输出：

| 构建输出中的内容 | 下一次运行 |
|---|---|
| 只有打包工具写下的内容和已停止的运行写下的内容 | 以退出码 `1` 失败。运行 `afterpack restore`，把输出恢复成打包工具写下的样子；或者从源代码重新构建后再运行。 |
| 一次重新构建，或者与打包工具写下的完全一致，没有留下已停止运行的任何内容 | 丢弃过期的备份并给出提示，然后运行。 |
| 混合状态：一些文件已重新构建，另一些仍保留已停止运行的输出 | 以退出码 `1` 失败并列出重新构建过的文件，`restore` 也会拒绝。请从源代码重新构建整个输出后再运行，或者从消息中给出的备份文件夹把其他文件的原始版本复制回去。 |

使用 [`--build.backup=false`](https://www.afterpack.dev/zh/docs/config#build-backup) 时不会写入备份，因此被终止的运行无法撤销：请从源代码重新构建。AfterPack 不会强制把写入刷到磁盘，因此如果在运行或恢复过程中断电，同样请从源代码重新构建。

### 并行运行与锁

运行和恢复在完成之前，都会持有其构建目录的锁 `.afterpack/backups/<build dir>/lock`。针对不同构建目录的运行（例如同一仓库中的两个应用）可以并行。针对同一构建的第二次运行，包括在保护 `.next/` 期间针对其内部目录（例如 `.next/static/chunks/`）的运行，会以退出码 `1` 失败，并指出持有锁的那次运行。如果锁所属的进程在本机上已经退出，或者锁已超过两个小时，它会被接管并给出提示。如果没有任何运行在进行而锁仍然存在，请删除消息中给出的那个文件。

## `afterpack audit <url>`

扫描一个**已部署**的站点，查找泄露的密钥、暴露的源代码和未受保护的 JavaScript，并在扫描进行的同时流式输出发现的问题。它不读取任何配置，除 URL 以外不发送任何内容。完整的命令参考见 [Audit](https://www.afterpack.dev/zh/docs/audit)。

```bash
npx afterpack@latest audit example.com
```

## 机器可读输出

`--diagnostics.format=json` 会在 stdout 上输出且仅输出一份 JSON 文档；所有面向人阅读的行都转到 stderr。通过标志、`AFTERPACK_diagnostics_format=json` 环境变量，或 `afterpack.json` 中的 `diagnostics.format` 都可以启用它，`verify` 和 `audit` 同样遵守。搭配 `diagnostics.level=none`，运行就只打印这份文档，别无其他。CI 步骤和 AI 代理应当读取这种格式，而不是解析文本输出；该包还附带一份 `SKILL.md`，为直接调用 CLI 的代理记录了 CLI 的用法。

```bash
npx afterpack@latest dist/ --diagnostics.format=json --diagnostics.level=none
```

## 退出码

| 代码 | 含义 |
|---|---|
| `0` | 成功。每个收集到的文件都已混淆并写入。[`--help` 和 `--version`](#usage) 也返回它。 |
| `1` | 失败。没有产出任何可用结果：路径不存在、路径下找不到 JavaScript、任何引擎错误、`verify` 失败、`audit` 扫描失败，或者带 Pro 密钥的构建其云端调用失败。 |
| `2` | 部分完成。为配额耗尽的构建预留；目前没有任何构建会返回它。 |
| `3` | 体积上限。在 [`inflation.max`](https://www.afterpack.dev/zh/docs/config#inflation-max) 的限制内无法达到复杂度目标（`DIAG_SIZE_CAP_REACHED`）。 |
| `4` | 需要 Pro。某个 [`regions`](https://www.afterpack.dev/zh/docs/config#regions) 条目把保护级别提升到超出整个程序的配置，需要 [`key`](https://www.afterpack.dev/zh/docs/config#key)（`DIAG_PRO_DIRECTIVE_REQUIRES_KEY`）。CLI 不读取源码中的指令，因此只有 `regions` 会走到这个退出码。 |
| `5` | 反射。检测到运行时反射，却没有设置 [`reflection.allow`](https://www.afterpack.dev/zh/docs/config#reflection-allow)（`DIAG_REFLECTION_NOT_ACKNOWLEDGED`）。 |
| `6` | 需要更新。AfterPack 云端，或当前的 `afterpack`，需要更新版本的 `@afterpack/core`。没有写出任何内容。 |
| `64` | 用法错误。未知的标志或命令、格式错误的值，或者重复的路径参数。 |

用 [`paths.exclude`](https://www.afterpack.dev/zh/docs/config#paths-exclude) 把必须原样发布的文件排除在外。

解析到 [`key`](https://www.afterpack.dev/zh/docs/config#key) 的 Pro 构建会失败即中止。如果云端调用失败，构建就停止，而不会回退到 Free 层级的输出。`4` 是 Free 构建遇到需要 key 的 `regions` 条目时的退出码；`5` 是任何构建（Free 或 Pro）遇到未确认的反射模式时的退出码；`2` 为将来配额耗尽的构建预留，目前没有任何构建会返回它。完整约定见[退出码](https://www.afterpack.dev/zh/docs/diagnostics#exit-codes)。

## 下一步

- [配置](https://www.afterpack.dev/zh/docs/config)：每个选项，以及能设置它的四种形式。
- [预设](https://www.afterpack.dev/zh/docs/presets)：五级梯度，以及每一级的代价。
- [Audit](https://www.afterpack.dev/zh/docs/audit)：完整的 `afterpack audit` 参考。
- [诊断](https://www.afterpack.dev/zh/docs/diagnostics#exit-codes)：退出约定、失败即中止规则，以及每个 `DIAG_*` 代码。
- [框架](https://www.afterpack.dev/zh/docs/frameworks)：插件路线，当你的技术栈有插件时是更好的选择。
- [快速上手](https://www.afterpack.dev/zh/docs/quickstart)：从安装到 [Protection Map](https://www.afterpack.dev/zh/docs/quickstart#check-the-result) 的最短路径。
