# 诊断（DIAG_* 参考）

AfterPack 发出的每一个 DIAG_* 代码、触发它的原因、如何解决，以及构建返回的退出代码。

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

AfterPack 绝不会交付比你要求的更弱的保护。当某个问题无法自动解决时，构建会带着一个具名的 `DIAG_*` 代码和具体的修复方法停止。

## 严重级别

AfterPack 发出的每一条诊断都恰好属于三种严重级别之一。不存在 `Warning` 级别。

| 严重级别 | 含义 | 会发生什么 |
|---|---|---|
| **Info** | 预期行为：你已经确认过这一点，或引擎采取了一种安全、保持正确性的回退方案。 | 构建成功，输出已写入。 |
| **Error** | 在不改动你这一端的情况下，无法满足你声明的要求（过紧的体积上限、解析错误、无效的密钥）。 | 非零退出，**不写入输出**。 |
| **Critical** | 引擎自身的缺陷。你这一端的任何操作都无法修复它。 | 非零退出，不写入输出，始终如此。[提交报告。](mailto:security@afterpack.dev) |

## 退出代码

`npx afterpack@latest` 会返回八种代码中的一种。可以直接在脚本中对它们做判断。

| 代码 | 含义 |
|---|---|
| `0` | 成功。所有被收集的文件都已混淆并写入。`--help` 和 `--version` 也以 `0` 退出。 |
| `1` | 失败。没有产生可用的输出，stderr 上的一条消息会说明发生了什么。 |
| `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` | 用法错误。未知的标志或命令、格式错误的值，或重复的路径参数。 |

### 什么会让构建以 64 退出

用法问题会在运行开始之前就将其终止。

- 未知的标志、未知的命令、格式错误的值，或多于一个的位置路径参数。用法说明会与错误一起重新打印。

### 什么会让构建以 1 退出

- 没有 `<path>` 参数，也没有找到构建输出（`dist/`、`build/`、`out/`、`.output/`、`.next/`，或 `package.json` 中所命名打包工具的惯例目录）。AfterPack 从不读取打包工具自身的配置文件，因此自定义的输出目录无法通过这种方式被检测到。
- 构建目录不存在，或不包含任何 `.js`/`.mjs`/`.cjs` 文件。空的构建目录会导致构建失败。常见原因是指向了错误的目录，或在构建之前就运行了它。
- 某个文件彻底失败，或在输入非空的情况下返回了空输出。
- 引擎无法安全混淆的文件。构建会失败并列出受影响的文件。使用 [`paths.exclude`](https://www.afterpack.dev/zh/docs/config#paths-exclude) 来划出一个必须原样交付的文件。
- `verify` 失败，或 `audit` 扫描失败。
- 下方任何带有 `Error` 或 `Critical` 严重级别的代码，`DIAG_SIZE_CAP_REACHED`（退出码 `3`）、`DIAG_PRO_DIRECTIVE_REQUIRES_KEY`（退出码 `4`）和 `DIAG_REFLECTION_NOT_ACKNOWLEDGED`（退出码 `5`）除外。

### 失败即中止，没有例外

一次 Pro 构建要么交付 Pro 级别的保护，要么就不交付。

*(fail-closed request-flow diagram — see the web page)*

一旦一个 [`key`](https://www.afterpack.dev/zh/docs/config#key) 从 [`AFTERPACK_key`](https://www.afterpack.dev/zh/docs/config#key) 或 `afterpack.json` 解析出来，这次构建就是一次云端构建。如果云端调用因任何原因失败，引擎会返回一个错误，构建以 `1` 退出；如果云端需要更新版本的 `@afterpack/core`，则以 `6` 退出。它不会回退到本地构建，也不会发出未混淆的输出。没有任何标志能交付未混淆的输出。让构建停下来，是为了把问题摆在你能看见的地方。

这一约定不会因调用路径而改变：`npx afterpack@latest` 与插件在打包结束时的自动运行表现完全相同。

### CI 中的退出代码

```bash
npx afterpack@latest dist/ || {
  echo "AfterPack failed: nothing shipped. See the error above."
  exit 1
}
```

非零退出会终止流水线。原因会出现在 stderr 上，以及下方的代码中。

## 完整参考

### 引擎

| 代码 | 含义 |
|---|---|
| `DIAG_TARGET_REACHED` | Info。文件的每个部分都达到了所请求的 [`complexity`](https://www.afterpack.dev/zh/docs/config#complexity)。 |
| `DIAG_TARGET_NOT_REACHED` | 从引擎 0.2.3 起为 Error，且不写出任何输出：文件的某一部分无法在你允许的变换下达到 [`complexity`](https://www.afterpack.dev/zh/docs/config#complexity) 目标，原因是 [`transforms.scopeDeepen.enabled`](https://www.afterpack.dev/zh/docs/config#transforms-kind-enabled) 为 `false`，或者某条[区域指令](https://www.afterpack.dev/zh/docs/directives#what-you-can-set)限制了这些行。消息会指出文件和行号。请重新开启该变换，为该文件或区域降低 `complexity`，或用 [`paths.exclude`](https://www.afterpack.dev/zh/docs/config#paths-exclude) 排除该文件。在引擎 0.2.2 中它是 Info：构建会交付层级更浅的输出，而由关闭的变换造成的不足可能被报告为 `DIAG_TARGET_REACHED`。 |
| `DIAG_INFLATION_BUDGET_HIT` | Info。仅在设置了有限 [`inflation.max`](https://www.afterpack.dev/zh/docs/config#inflation-max) 的非 floor 构建上触发：某个增大体积的阶段在到达目标前就停在该预算处。输出有效。从引擎 0.2.3 起，字符串常量 floor 绝不会向预算让步，因此在 floor 构建上这个代码不会触发，也绝不意味着有字符串字面量保持可读。在引擎 0.2.2 中，显式设置的 `inflation.max` 也可能让字符串编码中途停止；如果你设置过它，请用引擎 0.2.3 重新构建。 |
| `DIAG_SIZE_CAP_REACHED` | Error，退出码 `3`，且不写出任何输出：输出将达到你设置的 [`inflation.max`](https://www.afterpack.dev/zh/docs/config#inflation-max)。从引擎 0.2.3 起，最终生成的文件也会按这个上限检查，消息会给出两个字节数，并且字符串编码绝不会为了满足上限而中途停止。请调高 `inflation.max`、降低 [`complexity`](https://www.afterpack.dev/zh/docs/config#complexity)，或用 [`paths.exclude`](https://www.afterpack.dev/zh/docs/config#paths-exclude) 排除该文件。 |
| `DIAG_SELF_INTEGRITY_INERT` | Error，从引擎 0.2.3 起，仅限 Pro，且不写出任何输出：[`transforms.selfIntegrity.enabled`](https://www.afterpack.dev/zh/docs/config#transforms-kind-enabled) 已开启，但构建中没有可供它附着的、开启了 `transforms.crossDependency.enabled` 的 `medium` 或更高级别的代码。请把预设或某个区域提高到 `medium`，重新开启 `crossDependency`，或关闭 `selfIntegrity`。在引擎 0.2.2 中，这样的构建会在没有完整性校验的情况下成功。 |
| `DIAG_PATH_EXCLUDED` | Info。某个文件匹配了 [`paths.exclude`](https://www.afterpack.dev/zh/docs/config#paths-exclude) 并按字节原样交付；它的字节数被计为用户排除。 |
| `DIAG_DYNAMIC_SCOPE_NATIVE` | Info，从引擎 0.2.3 起。`with` 语句或直接 `eval` 让其周围的作用域保持原样：从它向外直到文件顶层的每一层作用域中，名称都保留原来的写法，`with` 语句体内的全局变量也保持原样。文件的其余部分照常混淆。要保护这些名称，如果被求值的代码不需要局部变量，就用间接方式调用 `eval`，即 `(0, eval)(code)`，或者把需要 `eval` 或 `with` 的代码移到单独的文件中。参见[语义契约](https://www.afterpack.dev/zh/docs/semantic-contract#what-stays-as-you-wrote-it)。 |
| `DIAG_SURVIVING_READABLE_LITERAL` | 残留的可读字符串字面量数量超过了 [`strings.leaks.max`](https://www.afterpack.dev/zh/docs/config#strings-leaks-max) 允许的上限。可以调高 [`complexity`](https://www.afterpack.dev/zh/docs/config#complexity)、降低 [`strings.minLength`](https://www.afterpack.dev/zh/docs/config#strings-minLength)，或调高该上限。 |
| `DIAG_SOURCE_MAP_INTEGRITY` | Info。提供的上游 `.map` 未能通过针对其输入的校验，因此该文件的 source map 被丢弃，混淆后的代码照常交付。缺失 map 无害；交付错误的 map 才是此处要避免的危害。 |
| `DIAG_OUTPUT_UNPARSEABLE` | Error，也是引擎缺陷的信号。重新解析输出失败，因此该文件失败即中止：不写出代码、source map 或保护图。 |
| `DIAG_PARSE_ERROR` | Error。某个输入代码块解析失败。AfterPack 严格运行在构建之后，作用于已生成的 JavaScript：先编译，再将它指向 JS 输出。 |
| `DIAG_CONFIG_PARSE_ERROR` | Error。[引擎配置](https://www.afterpack.dev/zh/docs/config) 的 JSON 未能反序列化。 |
| `DIAG_PRO_DIRECTIVE_REQUIRES_KEY` | Error，退出码 `4`，且不写出任何输出：Free 构建遇到了一条把保护提高到程序配置之上的[区域指令](https://www.afterpack.dev/zh/docs/directives#free-builds)。请设置 [`key`](https://www.afterpack.dev/zh/docs/config#key)，或删除该指令。从引擎 0.2.3 起，只降低保护的指令在 Free 上会被忽略；引擎 0.2.2 也会拒绝这类指令。 |
| `DIAG_INPUT_TOO_LARGE` | 单个输入超过了体积上限。 |
| `DIAG_BATCH_TOO_LARGE` | 一个批次的总体积超过 500 MB（413，不可重试）；构建以失败即中止的方式停止。 |
| `DIAG_INPUT_NESTING_EXCEEDS_ENVIRONMENT` | Error。输入的嵌套深度超过了解析器能安全处理的范围，在解析前就已估算，上限为 160 层。没有输出。请减少源代码的嵌套。 |
| `DIAG_OUTPUT_NESTING_EXCEEDS_ENVIRONMENT` | Error。混淆后的输出会嵌套超过 400 层，这是浏览器和 Worker 能安全解析的上限。没有输出。请针对该输入降低 [`complexity`](https://www.afterpack.dev/zh/docs/config#complexity)。 |
| `DIAG_ENGINE_BUG_NO_PROGRESS` | Critical。引擎没有任何进展。这是一个引擎缺陷。 |
| `DIAG_ENGINE_BUG_NO_APPLICABLE_TRANSFORM` | Critical。文件的某一部分最终低于复杂度目标，却没有任何关闭的变换或区域指令可以解释。这是一个引擎缺陷。 |
| `DIAG_ENGINE_BUG_PROCESS_THREAD_PANIC` | Critical。某个工作线程发生 panic。这是一个引擎缺陷。 |

### 构建插件 / CLI

| 代码 | 含义 |
|---|---|
| `DIAG_DIRECTIVE_UNKNOWN` | 一条 `@afterpack` 指令命名了一个无法识别的键。请对照[哪些键](https://www.afterpack.dev/zh/docs/directives#what-you-can-set)可以被某个区域覆盖来检查它。 |
| `DIAG_DIRECTIVE_UNSUPPORTED_FORM` | 该标记形式无法放置（`// @afterpack …` 行注释，或 `@afterpack:begin/:end` 区间）。请使用 `/* @afterpack … */` 块标记。 |
| `DIAG_DIRECTIVE_NOT_IMPLEMENTED` | 一个仅限 program 作用域的键被写进了指令中（例如 [`reflection.allow`](https://www.afterpack.dev/zh/docs/config#reflection-allow)），或者是一条多余的指令，如 `skip=false`。关于一个区域实际能覆盖什么，请参阅[哪些键](https://www.afterpack.dev/zh/docs/directives#what-you-can-set)。 |
| `DIAG_DIRECTIVE_SOURCE_UNRESOLVED` | Info。某条指令所在模块的位置无法映射到构建输出中，因此未被应用。请将被标记的代码移动到一个被导入的模块中。 |
| `DIAG_DIRECTIVE_DANGLING_END` | 存在一个 [`/* @afterpack end */`](https://www.afterpack.dev/zh/docs/directives#what-you-can-set)，但其上方没有对应的起始区块。 |
| `DIAG_DIRECTIVE_REGION_TO_FILE` | [`identifiers.globals.rename`](https://www.afterpack.dev/zh/docs/config#identifiers-globals-rename) 无法被限定在某个区域内，因此命名了它的指令会被忽略并报告。 |
| `DIAG_DIRECTIVE_COVERAGE_UNVERIFIED` | 一个携带指令的打包代码块[没有可解码的 source map](https://www.afterpack.dev/zh/docs/directives)，因此被标记的区域无法在其字节中被定位。 |
| `DIAG_DIRECTIVE_TARGET_ELIMINATED` | 指令在代码块的 map 中被定位到了，但匹配到零个范围。被标记的代码已被摇树优化移除，或者坐标没有对齐。 |
| `DIAG_ALREADY_OBFUSCATED` | 被收集的文件已经携带了上一次运行留下的 AfterPack 自身的混淆输出，通过内容哈希与[保护回执](https://www.afterpack.dev/zh/docs/cli#afterpack-verify-dir)匹配得出。AfterPack 的输出不是幂等的——请先从源代码重新构建。 |
| `DIAG_AFTERPACK_DIR_IN_OUTPUT` | 错误，且不会重写任何文件。构建输出中有一个 `.afterpack/` 文件夹，其中的备份、回执和 Protection Map 会随构建一起发布。删除它并重新构建。参见[`.afterpack/` 所在的位置](https://www.afterpack.dev/zh/docs/cli#files)。 |
| `DIAG_RECEIPT_UNWRITABLE` | 错误，且不会重写任何文件。无法把[保护回执](https://www.afterpack.dev/zh/docs/cli#files)写入 `.afterpack/receipts/`。让该文件夹可写，然后重新构建。 |

### 云端 API

| 代码 | 含义 |
|---|---|
| `DIAG_INVALID_KEY` | [`key`](https://www.afterpack.dev/zh/docs/config#key) 缺失、格式错误或未知（401）。请检查 [`AFTERPACK_key`](https://www.afterpack.dev/zh/docs/config#key)，或从[控制台](https://www.afterpack.dev/zh/docs/dashboard/api-keys)签发一个新密钥。 |
| `DIAG_KEY_REVOKED` | 该 API 密钥已被撤销（401）。 |
| `DIAG_KEY_EXPIRED` | 该 API 密钥已过期（401）。 |
| `DIAG_KEY_OUT_OF_SCOPE` | 请求的 IP 不在该密钥允许的范围内（403）。 |
| `DIAG_MISSING_IP` | 该请求未携带客户端 IP（400）。 |
| `DIAG_INVALID_MANIFEST` | 格式错误的 multipart 请求体，或缺少 `batch` 清单部分。 |
| `DIAG_FILE_TOO_LARGE` | 单个文件超过了单文件字节上限（413）。可以拆分它，或用 [`paths.exclude`](https://www.afterpack.dev/zh/docs/config#paths-exclude) 排除它。 |
| `DIAG_ENTITLEMENT_LAPSED` | 订阅付款已逾期（402）；更新账单信息以继续构建。 |
| `DIAG_QUOTA_EXHAUSTED` | 构建的总字节数超过了[剩余额度](https://www.afterpack.dev/zh/docs/tiers)，或达到了超量花费上限。每次构建只检查一次，且在任何文件分发之前，因此不会有已受保护的内容被降级。可以充值、升级，或调高上限；错误信息中会带有相应链接。 |
| `DIAG_RATE_LIMITED` | 超出速率限制（429）；请遵守 `Retry-After`。 |
| `DIAG_RATE_CHECK_UNAVAILABLE` | 速率限制检查本身失败，因此请求以失败即中止的方式停止（503）；请重试。 |
| `DIAG_BUILD_DEADLINE` | 在所有文件都开始处理之前，实际耗时预算已用尽；未完成的文件会被列出。 |
| `DIAG_ENGINE_FAILURE` | 云端引擎在某个文件上失败。 |

### 运行时反射

AfterPack 会检测以下[运行时反射](https://www.afterpack.dev/zh/docs/config#reflection-allow)模式：`functionToString`、`nameIntrospection`、`argumentsCallee`、`prototypeChain`、`decoratorMetadata` 和 `constructorIntrospection`。每一次检测都携带一个代码：已确认的形式在代码中带有模式名称，未确认的形式是单一代码，其消息会指明该模式。

| 代码 | 含义 |
|---|---|
| `DIAG_REFLECTION_nameIntrospection_ACKNOWLEDGED` | Info。该模式被检测到并被保留，因为你已经确认过它。每种模式对应一个代码；中间的名称可以是上面任意一种模式。 |
| `DIAG_REFLECTION_NOT_ACKNOWLEDGED` | Error，退出码 `5`。检测到某个反射模式但**没有**被确认，因此构建会停止，而不是交付一段运行时读取会出错的代码；消息会指明该模式。可以用 [`reflection.allow`](https://www.afterpack.dev/zh/docs/config#reflection-allow) 确认它——它也接受 `angular` 和 `nestjs`——或者用 [`paths.exclude`](https://www.afterpack.dev/zh/docs/config#paths-exclude) 把该文件排除在外。 |

> **散落的产物会被 audit 捕获**
>
> 将一个散落的 [`.map`](https://www.afterpack.dev/zh/docs/config#sourceMap-enabled) 文件或一个未混淆的同名文件交付进你的部署目录，会被 [`npx afterpack@latest audit`](https://www.afterpack.dev/zh/docs/audit) 捕获，而不是被构建本身捕获。
>
> 如果某个代码缺失或有误，请提交一个 issue：[github.com/afterpack-dev/afterpack/issues](https://github.com/afterpack-dev/afterpack/issues)。

使用 [`--diagnostics.level=all`](https://www.afterpack.dev/zh/docs/config#diagnostics-level) 运行，可以在任意构建上获得完整的诊断流，而不是默认的摘要。在插件构建中，同样的开关是 [`diagnostics.level`](https://www.afterpack.dev/zh/docs/config#diagnostics-level) 选项，或 [`AFTERPACK_diagnostics_level`](https://www.afterpack.dev/zh/docs/config#diagnostics-level)。

## 下一步

- [CLI 参考](https://www.afterpack.dev/zh/docs/cli)：上方代码所引用的每一个标志。
- [配置](https://www.afterpack.dev/zh/docs/config)：这些代码所引用的配置键。
- [语义契约](https://www.afterpack.dev/zh/docs/semantic-contract)：输出保持不变的部分，以及少数有意差异。
- [指令](https://www.afterpack.dev/zh/docs/directives)：上方指令代码背后完整的 `@afterpack` 语法。
- [最佳实践](https://www.afterpack.dev/zh/docs/best-practices)：从一开始就避免这些诊断的模式。
- [套餐](https://www.afterpack.dev/zh/docs/tiers)：什么是免费的，什么需要一个 [`key`](https://www.afterpack.dev/zh/docs/config#key)。
