> ## Documentation Index
> Fetch the complete documentation index at: https://opc-43d279b8.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# SourceMap

> 覆盖 SourceMap 结构、VLQ 编码、devtool 选型、sourcesContent 与线上报错定位方案。

> 线上报错只给出一行压缩后的行号时，我才真正去读 SourceMap 的 mappings，也才明白它是怎么把产物位置映射回源码的。

***

## Q1: SourceMap 是什么？解决了什么问题？

**1. 前端工具链的演进**

现代前端开发中，源码要经过 TypeScript 编译、Babel 转译、Webpack 打包、Terser 压缩等多层处理。最终产物是高度压缩的 bundle，变量名被缩短、代码被合并、行号完全错乱。

**2. 致命痛点: 生产环境报错无法定位**

用户在生产环境遇到报错，错误堆栈指向的是 `bundle.min.js:1:23456`。你完全不知道这对应源码的哪个文件、哪一行、哪个变量。调试变成了"猜谜游戏"。

**3. SourceMap 的引入**

SourceMap 是一个 JSON 文件，记录了**编译后代码的每个位置对应源码的哪个位置**。浏览器的 DevTools 读取 SourceMap 后，可以把压缩后的错误堆栈映射回源码级别。

**4. 核心机制: VLQ 编码的映射关系**

SourceMap 的 `mappings` 字段用 VLQ（Variable-Length Quantity）编码，把多个数字压缩为 Base64 字符串。每个映射是相对于前一个映射的偏移量（差分编码），大大减小了文件体积。

**5. 关键权衡: 安全性 vs 可调试性**

完整的 SourceMap 包含源码内容，直接暴露给用户会泄露知识产权。所以生产环境需要在"能定位报错"和"不暴露源码"之间做权衡。

**SourceMap 解决的核心问题：**

```text theme={null}
问题：编译产物是压缩混淆的代码，报错时无法定位源码

没有 SourceMap：bundle.min.js:1:23456 → 不知道对应源码哪里

解决：记录编译产物和源码之间的位置映射关系

有 SourceMap：bundle.min.js:1:23456 → src/components/UserProfile.tsx:45:12
```

> [!NOTE]
>
> SourceMap 是编译产物和源码之间的"翻译词典"，让压缩后的报错能精准映射回源码位置。

> [card] 🚫 没有 SourceMap
>
> 你在生产环境收到一条报错：`TypeError: Cannot read property 'name' of undefined at bundle.min.js:1:23456`。你对着压缩后的代码看了半小时，完全不知道是哪个组件的哪一行出了问题。

> [card] ✅ 引入 SourceMap
>
> 同样的报错，Sentry 后台直接显示：`src/components/UserProfile.tsx:45:12 - user.name`。你一眼就看到了问题：`user` 对象可能是 `undefined`，需要加空值判断。

> [!TIP]
>
> 回答时先说"为什么"（编译产物无法调试），再说"怎么解决"（VLQ 编码的位置映射）。想听的是你对 SourceMap 工程价值的理解，而不是背 JSON 结构。

***

## Q2: SourceMap 的类型有哪些？怎么选？

SourceMap 的本质是**平衡构建速度、文件体积和安全性**。核心逻辑只有 2 步：

```text theme={null}
┌───────────────────────────────────────┐
│      开发环境：重建速度与质量优先        │  ← eval-source-map（首建慢、重建最快）
└─────────────────┬─────────────────────┘
                  │ 选择标准：重建速度 > 首次构建速度
                  ▼
┌───────────────────────────────────────┐
│         生产环境：安全优先               │  ← hidden-source-map（生产常用）
└───────────────────────────────────────┘
                  │ 选择标准：安全性 > 可调试性
```

```javascript theme={null}
// webpack.config.js 中的 devtool 配置

// 开发环境：eval-source-map（首次构建慢，重建最快）
// - 用 eval() 执行代码，SourceMap 内联在 eval 中
// - 重建速度最快，但首次构建稍慢
devtool: "eval-source-map";

// 生产环境：hidden-source-map（最安全）
// - 生成 SourceMap，但不在 JS 文件中引用（不加 //# sourceMappingURL）
// - 用户看不到源码，但我们可以上传到 Sentry
devtool: "hidden-source-map";
```

> [!WARNING]
>
> 不同 `devtool` 配置的安全等级差异巨大：
>
> * **`source-map`**：生成完整 SourceMap 并在 JS 文件末尾引用。任何人打开 DevTools 都能看到源码。**绝不能用在生产环境**。
> * **`hidden-source-map`**：生成 SourceMap 但不引用。用户看不到，但可以手动上传到 Sentry。**生产环境推荐**。
> * **`nosources-source-map`**：只包含位置信息，不包含源码内容。报错能定位到源码位置，但看不到源码。**高安全要求场景**。
> * **`eval-source-map`**：用 eval 执行，SourceMap 内联。**开发环境推荐**。

> [!TIP]
>
> SourceMap 就像一本导航地图。开发环境（`eval-source-map`）时，你把地图摊开放在桌上，随时都能看，找路最快。生产环境（`hidden-source-map`）时，你把地图锁在保险柜里（上传到 Sentry），用户看不到，但你有钥匙——出问题时打开保险柜就能精准定位。

### 各种 devtool 配置的性能对比

| 配置 | 构建速度 | 重建速度 | 质量 | 适用场景 |
| - | - | - | - | - |
| `eval` | 最快 | 最快 | 无 SourceMap | 快速开发，不需要调试 |
| `eval-source-map` | 最慢（首次） | 最快 | 完整 | 开发环境推荐 |
| `cheap-module-source-map` | 较快 | 较快 | 只映射行号 | 大型项目开发 |
| `source-map` | 慢 | 慢 | 完整 | 仅内网调试/错误监控平台私有上传 |
| `hidden-source-map` | 慢 | 慢 | 完整但不引用 | 生产环境推荐 |
| `nosources-source-map` | 慢 | 慢 | 无源码内容 | 高安全要求场景 |

### SourceMap 的安全等级详解

不同 `devtool` 配置的安全等级差异巨大：

```javascript theme={null}
// 最危险：source-map（生产环境禁用）
// 生成完整 SourceMap 并在 JS 文件末尾引用
// 任何人打开 DevTools 都能看到源码
devtool: "source-map";

// 安全：hidden-source-map（生产环境推荐）
// 生成 SourceMap 但不引用（不加 //# sourceMappingURL）
// 用户看不到，但可以上传到 Sentry
devtool: "hidden-source-map";

// 最安全：nosources-source-map
// 只包含位置信息，不包含源码内容
// 报错能定位到源码位置，但看不到源码
devtool: "nosources-source-map";
```

***

## Q3: 生产环境关掉了 SourceMap，报错了怎么定位源码？

### 基础梳理

| 步骤 | 工具 | 说明 |
| - | - | - |
| **构建时** | Webpack + Sentry Plugin | 生成 SourceMap 并自动上传到 Sentry |
| **运行时** | Sentry SDK | 收集错误堆栈（压缩后的行号列号） |
| **映射时** | Sentry 后台 | 用 SourceMap 把行号映射回源码 |
| **查看时** | Sentry Web UI | 开发者看到源码级别的报错信息 |

### 专家视角深度剖析

很多人能说出"用 hidden-source-map + Sentry"，但实际更在意的是：**SourceMap 的 `mappings` 字段到底存了什么？VLQ 编码是怎么压缩映射关系的？**

```javascript theme={null}
// SourceMap JSON 结构
{
  "version": 3,                    // 版本号，固定为 3
  "file": "bundle.min.js",         // 编译后的文件名
  "sources": [                     // 源文件列表
    "src/index.js",
    "src/utils.js"
  ],
  "sourcesContent": ["..."],       // 源码内容（可选；nosources-source-map 不写该字段）
  "names": ["add", "a", "b"],     // 变量名列表
  "mappings": "AAAA,SAASA..."     // VLQ 编码的映射关系（核心）
}

// mappings 中每一段由 VLQ 字段组成，常见有三种段：
// 1 字段：只有编译后列号偏移（表示该段没有对应源码映射）
// 4 字段：编译后列号 + 源文件索引 + 源码行号 + 源码列号（无变量名）
// 5 字段：在 4 字段基础上再加变量名索引

// 所有偏移都是相对于前一个映射的差分值（差分编码）
// 这使得大量连续映射的数字都很小，VLQ 编码后体积很小
```

```javascript theme={null}
// Sentry 的完整配置流程

// 1. webpack.config.js
const { sentryWebpackPlugin } = require("@sentry/webpack-plugin");

module.exports = {
  devtool: "hidden-source-map", // 生成但不引用
  plugins: [
    sentryWebpackPlugin({
      org: "my-org",
      project: "my-project",
      authToken: process.env.SENTRY_AUTH_TOKEN,
    }),
  ],
};

// 2. 前端代码中初始化 Sentry SDK
import * as Sentry from "@sentry/react";
Sentry.init({
  dsn: "https://xxx@sentry.io/xxx",
});

// 3. 运行时 Sentry 自动收集错误堆栈
// 4. Sentry 后台用上传的 SourceMap 映射回源码
```

### Sentry 的完整配置流程

Sentry 的 SourceMap 处理分为四步：

```text theme={null}
1. 构建时：webpack-plugin 自动上传 SourceMap 到 Sentry 服务器
2. 运行时：Sentry SDK 收集错误堆栈（压缩后的行号列号）
3. 映射时：Sentry 后台用 SourceMap 把行号映射回源码
4. 查看时：开发者在 Sentry Web UI 看到源码级别的报错信息
```

### SourceMap 的 JSON 结构详解

```javascript theme={null}
{
  "version": 3,                    // 版本号，固定为 3
  "file": "bundle.min.js",         // 编译后的文件名
  "sources": [                     // 源文件列表
    "src/index.js",
    "src/utils.js"
  ],
  "sourcesContent": ["..."],       // 源码内容（可选；nosources-source-map 不写该字段）
  "names": ["add", "a", "b"],     // 变量名列表
  "mappings": "AAAA,SAASA..."     // VLQ 编码的映射关系（核心）
}
```

***

## Q4: 多个 SourceMap 怎么合并？

**场景：TypeScript → Babel → Webpack，每个工具生成自己的 SourceMap。**

```javascript theme={null}
// 链式处理时，SourceMap 需要逐层合并：

// 第 1 层：TypeScript 编译
// src/index.ts → dist/index.js + index.js.map
// map 记录：dist/index.js → src/index.ts

// 第 2 层：Babel 转译
// dist/index.js → dist/compiled.js + compiled.js.map
// map 记录：dist/compiled.js → dist/index.js

// 第 3 层：Webpack 打包 + 压缩
// dist/compiled.js → bundle.min.js + bundle.min.js.map
// map 记录：bundle.min.js → dist/compiled.js

// 最终需要合并：bundle.min.js → src/index.ts（跳过中间步骤）

// 使用 source-map 库合并：
const { SourceMapConsumer } = require("source-map");

async function mergeSourceMaps(finalMap, intermediateMap) {
  const consumer = await new SourceMapConsumer(finalMap);
  const composed = consumer.applySourceMap(
    await new SourceMapConsumer(intermediateMap),
  );
  return composed.toJSON();
}
```

> [!TIP]
>
> 大多数情况下不需要手动合并 SourceMap。Webpack 的 `devtool` 配置会自动处理 Loader 链的 SourceMap 合并。只需要确保每个 Loader（如 `ts-loader`、`babel-loader`）开启了 `sourceMap: true`，Webpack 会自动合并成最终的 SourceMap。

***

## 🔍 底层源码：VLQ 编码的实现

SourceMap 的 VLQ 编码到底是怎么压缩映射关系的？

VLQ（Variable-Length Quantity）是一种变长编码方式，用 Base64 字符表示数字（详见 [Source Map Revision 3 Proposal](https://sourcemaps.info/spec.html)）：

```javascript theme={null}
// VLQ 编码的核心逻辑：
// 1. 把数字转为二进制
// 2. 每 5 位一组（因为 Base64 有 64 个字符，取 5 位数据 + 1 位延续标志）
// 3. 最低位是符号位（0 正 1 负）
// 4. 每组的最高位是延续标志（1 = 还有下一组，0 = 这是最后一组）
// 5. 用 Base64 字符表映射为可打印字符

// 示例：编码数字 5
// 5 的二进制：101
// 正数符号位为 0，左移一位拼在最低位：1010
// 分组（5 位一组）：01010
// 无后续分组，延续标志为 0：01010 → 十进制 10 → Base64 字符表第 10 位 → 'K'

// mappings 字段的结构：
// 每行用 ; 分隔
// 每行内的映射用 , 分隔
// 每个映射由 1、4 或 5 个 VLQ 值组成（1 字段表示无源码映射，常见为 4/5 字段）
// 所有值都是相对于前一个映射的差分值
```

Webpack 的 SourceMapDevToolPlugin 是怎么生成 SourceMap 的？

Webpack 的 `SourceMapDevToolPlugin` 在代码生成阶段，为每个模块生成独立的 SourceMap，然后合并成最终的 SourceMap（详见 [Webpack 源码：lib/SourceMapDevToolPlugin.js](https://github.com/webpack/webpack/blob/main/lib/SourceMapDevToolPlugin.js)）：

```javascript theme={null}
// Webpack SourceMap 生成流程：
// 1. 每个模块经过 Loader 处理时，Loader 生成模块级 SourceMap
// 2. Webpack 把所有模块拼接成 bundle 时，记录每个模块在 bundle 中的偏移量
// 3. 用 source-map 库的 SourceMapGenerator 合并所有模块级 SourceMap
// 4. 输出最终的 .map 文件

// 关键：如果 Loader 没有开启 sourceMap，Webpack 无法生成高质量的 SourceMap
// 所以 ts-loader、babel-loader 都需要配置 sourceMap: true
```

***

## 十、及格回答 vs 满分回答

### 问题："生产环境怎么处理 SourceMap？"

**及格回答（60-70分）：**

> "生产环境用 hidden-source-map，生成但不引用。用 Sentry 上传 SourceMap，报错时自动映射回源码。"

问题出在：**知道方案但不理解原理**。

**满分回答（90-100分）：**

> "生产环境的 SourceMap 策略要平衡**安全性**和**可调试性**。`hidden-source-map` 生成 SourceMap 但不在 JS 文件中引用（不加 `//# sourceMappingURL`），用户看不到源码，但我们可以上传到 Sentry 等监控平台。这是最常见的方案。<sup>\[1]</sup>"

> "SourceMap 的 `mappings` 字段用 VLQ（Variable-Length Quantity）编码——一种变长编码方式，把多个数字压缩为 Base64 字符串。每个映射通常是 4 或 5 个字段的组（编译后列号偏移、源文件索引偏移、源码行号偏移、源码列号偏移，变量名索引可选；也允许只有 1 个字段表示无源码映射），所有偏移都是相对于前一个映射的差分值，大大减小了文件体积。<sup>\[2]</sup>"

> "Sentry 的 SourceMap 处理流程：构建时通过 webpack-plugin 把 SourceMap 上传到 Sentry 服务器。运行时 Sentry SDK 收集错误堆栈（压缩后的行号列号）。Sentry 后台用 SourceMap 把行号映射回源码，开发者在后台看到源码级别的报错。对于 TypeScript → Babel → Webpack 的链式处理，Webpack 会自动合并各级 SourceMap，不需要手动处理。<sup>\[3]</sup>"

### 常见追问

> [accordion] SourceMap 的 VLQ 编码是什么?为什么用差分编码?
>
> **答题要点：**
> 说出 VLQ 的编码原理和差分编码的优势。
>
> **高分回答：**
> "VLQ 是变长编码，用 Base64 字符表示数字。每个字符的低 5 位是数据，最高位是延续标志。mappings 中的每个映射由 1、4 或 5 个 VLQ 字段组成：1 字段表示无源码映射；4 字段为列号偏移、源文件索引偏移、源码行号偏移、源码列号偏移；5 字段再加名称索引。
>
> 为什么用差分编码（所有值都是相对于前一个映射的偏移量）？因为相邻字符的映射通常只差几个位置，差分后的数字很小（0、1、2），VLQ 编码后只需要 1 个 Base64 字符。如果用绝对值，数字可能很大（如 23456），需要多个字符。差分编码能把 mappings 的体积大幅压缩。"

> [accordion] SourceMap 会影响构建速度吗?怎么优化?
>
> **答题要点：**
> 说出不同 devtool 配置对构建速度的影响。
>
> **高分回答：**
> "会。生成 SourceMap 需要额外的计算——Webpack 的 SourceMapDevToolPlugin 需要遍历所有模块，生成映射关系并合并。不同配置的速度差异很大：
>
> * `eval`：最快，不生成 SourceMap，用 eval 执行
> * `eval-source-map`：首次构建最慢，但重建最快，SourceMap 内联在 eval 中
> * `cheap-module-source-map`：只映射行号（不映射列号），速度较快
> * `source-map`：较慢，完整映射
>
> 开发环境推荐 `eval-source-map`（重建速度最快）。如果项目很大导致首次构建慢，可以先用 `eval`（无 SourceMap），等需要调试时再切到 `eval-source-map`。"

> [accordion] 多个工具链式处理时, SourceMap 怎么合并?
>
> **答题要点：**
> 说出链式 SourceMap 的合并原理。
>
> **高分回答：**
> "当多个工具链式处理时（如 TypeScript → Babel → Webpack），每个工具生成自己的 SourceMap。合并用 `source-map` 库的 `SourceMapConsumer.applySourceMap` 方法——它接受两个 SourceMap，生成一个从最终产物直接映射到原始源码的合并 SourceMap。
>
> 实际开发中不需要手动合并。Webpack 的 `devtool` 配置会自动处理 Loader 链的 SourceMap 合并。只需要确保每个 Loader 开启了 `sourceMap: true`（如 `ts-loader` 的 `sourceMap: true`、`babel-loader` 的 `sourceMaps: true`），Webpack 会自动合并成最终的 SourceMap。"

> [accordion] nosources-source-map 和 hidden-source-map 有什么区别?
>
> **答题要点：**
> 对比两种配置的安全性和可调试性。
>
> **高分回答：**
> "`hidden-source-map`：生成完整的 SourceMap（包含源码内容），但不在 JS 文件中引用。用户看不到 SourceMap，但如果你把 .map 文件上传到 Sentry，Sentry 可以用它映射回完整的源码。
>
> `nosources-source-map`：生成 SourceMap，但不包含 `sourcesContent`（源码内容被移除）——只有位置信息（行号、列号），不包含源码内容。报错时能定位到源码位置（哪个文件第几行），但看不到源码内容。
>
> 选择标准：如果 SourceMap 只存在 Sentry 服务器上（用户无法访问），用 `hidden-source-map`（信息最全）。如果 SourceMap 可能被用户获取（如放在 CDN 上），用 `nosources-source-map`（不泄露源码内容）。"

> [accordion] SourceMap 的 VLQ 编码原理是什么?
>
> **答题要点：**
> 说出 VLQ 的编码方式和差分编码的优势。
>
> **高分回答：**
> "VLQ（Variable-Length Quantity）是一种变长编码方式，用 Base64 字符表示数字。每个字符的低 5 位是数据，最高位是延续标志。
>
> mappings 中的每个映射由 1、4 或 5 个 VLQ 字段组成：1 字段表示无源码映射；4 字段为列号偏移、源文件索引偏移、源码行号偏移、源码列号偏移；5 字段再加名称索引。
>
> 为什么用差分编码（所有值都是相对于前一个映射的偏移量）？因为相邻字符的映射通常只差几个位置，差分后的数字很小（0、1、2），VLQ 编码后只需要 1 个 Base64 字符。如果用绝对值，数字可能很大（如 23456），需要多个字符。差分编码能把 mappings 的体积大幅压缩。"

> [accordion] SourceMap 会影响构建速度吗?
>
> **答题要点：**
> 说出不同 devtool 配置对构建速度的影响。
>
> **高分回答：**
> "会。生成 SourceMap 需要额外的计算——Webpack 的 SourceMapDevToolPlugin 需要遍历所有模块，生成映射关系并合并。不同配置的速度差异很大：
>
> * `eval`：最快，不生成 SourceMap，用 eval 执行
> * `eval-source-map`：首次构建最慢，但重建最快，SourceMap 内联在 eval 中
> * `cheap-module-source-map`：只映射行号（不映射列号），速度较快
> * `source-map`：较慢，完整映射
>
> 开发环境推荐 `eval-source-map`（重建速度最快）。如果项目很大导致首次构建慢，可以先用 `eval`（无 SourceMap），等需要调试时再切到 `eval-source-map`。"

> [accordion] 多个工具链式处理时, SourceMap 怎么合并?
>
> **答题要点：**
> 说出链式 SourceMap 的合并原理。
>
> **高分回答：**
> "当多个工具链式处理时（如 TypeScript → Babel → Webpack），每个工具生成自己的 SourceMap。合并用 `source-map` 库的 `SourceMapConsumer.applySourceMap` 方法——它接受两个 SourceMap，生成一个从最终产物直接映射到原始源码的合并 SourceMap。
>
> 实际开发中不需要手动合并。Webpack 的 `devtool` 配置会自动处理 Loader 链的 SourceMap 合并。只需要确保每个 Loader 开启了 `sourceMap: true`（如 `ts-loader` 的 `sourceMap: true`、`babel-loader` 的 `sourceMaps: true`），Webpack 会自动合并成最终的 SourceMap。"

> [accordion] SourceMap 在 CI/CD 中怎么处理?
>
> **答题要点：**
> 说出 SourceMap 在 CI/CD 中的上传和管理策略。
>
> **高分回答：**
> "在 CI/CD 中，SourceMap 的处理流程：
>
> 1. **构建时**：用 `sentry-webpack-plugin` 自动生成 SourceMap 并上传到 Sentry
> 2. **部署时**：只部署 JS 文件，不部署 .map 文件（避免泄露源码）
> 3. **版本管理**：每次部署生成唯一的 release ID（如 commit hash），Sentry 根据 release ID 匹配 SourceMap
>
> ```yaml theme={null}
> # GitHub Actions 配置
> - run: npm run build
> - run: npx @sentry/cli sourcemaps upload --release ${{ github.sha }} ./dist
> ```
>
> 坑点：如果 SourceMap 上传失败，Sentry 无法映射源码。建议在 CI 中检查上传结果，失败时告警。"

> [accordion] 🔥 SourceMap 泄露了源码怎么办?
>
> **答题要点：**
> 说出 SourceMap 泄露的风险和预防措施。
>
> **高分回答：**
> "SourceMap 泄露的风险：
>
> 1. **知识产权泄露**：竞争对手可以看到你的业务逻辑
> 2. **安全漏洞暴露**：攻击者可以看到 API 密钥、内部接口
> 3. **代码审计**：任何人都可以审计你的代码
>
> 预防措施：
>
> 1. **生产环境禁用 source-map**：用 `hidden-source-map` 或 `nosources-source-map`
> 2. **不部署 .map 文件**：构建后删除 .map 文件，只上传到 Sentry
> 3. **Sentry 权限控制**：设置 Sentry 项目的访问权限，只允许团队成员查看
> 4. **定期检查**：用脚本检查生产环境是否意外暴露了 .map 文件
>
> 补救措施：如果已经泄露，立即删除 .map 文件，轮换 API 密钥，检查是否有安全漏洞。"

> [accordion] 🔥 怎么调试第三方库的 SourceMap?
>
> **答题要点：**
> 说出第三方库 SourceMap 的获取和使用方法。
>
> **高分回答：**
> "调试第三方库的 SourceMap 有三种方式：
>
> 1. **npm 包自带 SourceMap**：有些 npm 包（如 lodash-es）在发布时包含了 SourceMap。在 `node_modules` 中找到 `.map` 文件即可。
> 2. **从源码构建**：clone 第三方库的源码，自己构建并生成 SourceMap。适合需要深入调试的场景。
> 3. **浏览器 DevTools**：Chrome DevTools 支持加载本地 SourceMap。在 Sources 面板中右键文件 → Add source map，输入 .map 文件的路径。
>
> 坑点：第三方库的 SourceMap 可能和你使用的版本不匹配。建议用 `npm list <package>` 确认版本，然后从对应版本的源码构建 SourceMap。"

***

## 📚 延伸阅读与引用参考

* 🔗 [Source Map Revision 3 Proposal](https://sourcemaps.info/spec.html)：*SourceMap 规范的权威定义，包含 VLQ 编码的完整说明。* <sup>\[1]</sup>
* 📖 [Sentry Source Maps 文档](https://docs.sentry.io/platforms/javascript/sourcemaps/)：*Sentry 官方对 SourceMap 上传和映射流程的详细说明。* <sup>\[2]</sup>
* 🟢 [source-map npm 包](https://github.com/mozilla/source-map)：*Mozilla 实现的 SourceMap 解析和合并库，Webpack 底层依赖它处理 SourceMap。* <sup>\[3]</sup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.