flow-checker
映射一个Next.js 应用路由器 项目(它的页面,组件,请求,类型和用户流),当你使用应用程序时,根据实际流量检查地图,并将其导出为文档。
npm install -D @integr8-media/flow-checker
npx flow-checker init # adds two capture hooks and a config file
npx flow-checker # scans, opens the dashboard and starts your app with capture on
然后像往常一样使用你的应用。应用程序发送的每个请求都显示在仪表板上,并链接到发送请求的代码行。
它是做什么的
- 扫描代码。 路由(组,动态,捕获,并行和拦截段,next-intl)
[locale]),具有服务器/客户端边界的组件树,以及每个请求:fetch、axios、您自己的包装器客户端(以及您列出的任何HTTP库或API客户端包)apiClients), React Query, SWR, tRPC (v10和v11: hooks, vanilla客户端和v11)queryOptions()GraphQL(阿波罗客户端3和4,urql和Relay:按名称操作,从代码根,Relay编译器或模式键入),服务器发送的事件(new EventSource()和流fetch,每个监听的事件及其数据类型),WebSockets (new WebSocket()和套接字。服务器操作(Server Actions)和路由处理程序(包括到后端的第二跳)。它还可以找到页面之间的导航(Link,router.push,redirect),并推断用户流,例如 登录→验证OTP→仪表盘. 中间件(middleware.ts,或者proxy.ts在Next.js上16)也被读取:它的路由config.matcher它的选择,它的重定向(以及每个重定向的条件),它的重写(用于告诉哪个页面响应了URL)以及它发送的请求。请求主体、响应和道具的类型由TypeScript编译器完全解析。 - 捕捉真实流量 在浏览器和服务器上进行测试时,使用密码、令牌和验证头 在任何东西被保存之前被蒙面.
- 验证映射。 每个捕获都与发送它的请求站点相匹配,从而标记该站点
✓ verified. 将捕获的数据与声明的类型(缺失字段、额外字段、错误类型、意外)进行比较null),当你走的时候,流是确定的。 - 根据Postman集合检查代码 (可选):集合没有的请求,端点没有调用,以及保存示例中的类型。
- 跟上你的编辑。 文件被监视并逐渐重新扫描。捕获后代码发生变化的请求将被标记
↻ needs re-check. - 出口文档: Markdown(带有Mermaid路线图和序列图)和仪表板的静态只读副本,您可以在任何地方托管。
要求
| 支持 | |
|---|---|
| Next.js | 13.4及以后的版本,仅支持应用路由器 pages/ 未扫描目录) |
| node . js | 22.12或更高版本(22.18+)加载 flow-checker.config.ts) |
| 操作系统 | Windows, macOS, Linux |
| 软件包管理器 | Npm, pnpm, yarn, bun(从lockfile中检测) |
15.3之前的Next.js
js 15.3添加了 instrumentation-client 加载浏览器钩子的文件。在13.4-15.2中,用 withFlowChecker() 所以相同的钩子文件工作(在13.4-14)。X也打开 experimental.instrumentationHook 对于服务器钩子):
// next.config.mjs
import { withFlowChecker } from '@integr8-media/flow-checker/next'
export default withFlowChecker({
// your config
})
// next.config.js (CommonJS)
const { withFlowChecker } = require('@integr8-media/flow-checker/next')
module.exports = withFlowChecker({})
除非应用程序是由flow-checker启动的,否则它不会改变任何东西,并且在15.3及以后的版本中返回未修改的配置。在这些版本中,浏览器捕获需要webpack: run next dev 没有 --turbo. flow-checker init 和 flow-checker doctor 当你需要的时候告诉你。
命令
flow-checker # same as `dev`
flow-checker dev # scan + watch + dashboard + start your app with capture on
flow-checker scan # scan only, write .flow-checker/model.json
flow-checker init # add the capture hooks, a config file and .gitignore entries
flow-checker export # Markdown docs and/or a static HTML site
flow-checker doctor # check Node, Next.js, hooks, tsconfig, ports and .gitignore
flow-checker postman import # read a Postman collection to check the code against
| 选项 | 命令 | 意义 |
|---|---|---|
--root <dir> | 所有 | 项目根目录(默认:当前目录) |
--app-dir <dir> | 所有 | 应用路由器目录(默认: src/app 或者 app) |
--config <file> | Dev,扫描,导出,医生 | 配置文件(默认: flow-checker.config.* 在根) |
--port <n> | dev,医生 | 仪表板端口(默认为4400) |
--app-port <n> | dev,医生 | 你的应用运行端口(默认为3000) |
--dev-command <cmd> | dev | 启动应用程序的命令(默认:the dev 脚本,或 next dev) |
--no-app | dev | 不要启动应用程序,用环境自己启动它 dev 打印。 |
--no-open | dev | 不要打开浏览器 |
--no-watch | dev | 当文件更改时,不要重新扫描 |
-y, --yes | 初始化 | 无需询问就应用更改 |
--tree, --types, --requests, --flows | 扫描 | 还要打印路由/组件树、解析类型、请求站点或流 |
--md <dir>, --html <dir> | 出口 | 将Markdown文档/静态站点写入此文件夹。Neither: both,放到配置的文件夹中。 |
--raw | 出口 | 将捕获保持为已保存状态,而不必再次编辑它们 |
--force | 出口 | 写入包含其他文件的文件夹 |
--collection <src> | 邮差进口 | v2.1 JSON文件,v3 YAML文件夹或云集合id(默认: postman.collection) |
--environment <src> | 邮差进口 | 环境文件或云id {{variables}} |
--api-key-env <name> | 邮差进口 | 带有Postman API密钥的Env变量,用于云id(默认) POSTMAN_API_KEY) |
--verbose | 所有 | 还要打印解析后的配置、扫描运行的位置和其他详细信息 |
-q, --quiet | 所有 | 没有进度输出:只有结果、警告和错误 |
每个命令在stderr上显示其进度。在终端中,活跃线显示了一个旋转器、一个用于整个命令的条、当前步骤及其时间,并且每个完成的步骤都会留下一个 ✓ 与它的发现相符:
✓ Loaded: 1,806 files (3.8 s)
✓ Routes: 401 pages, 200 route handlers (0.2 s)
⠸ [█████████░░░░░░░░░░░] 47% Walking components… route 300/601 1.3 s
在CI中,当输出是管道的时候,或者使用 NO_COLOR / TERM=dumb,只有 ✓ 行作为纯文本打印。
指示板
flow-checker dev 服务于端口4400上的仪表板。相同的屏幕位于静态HTML导出中,只读。
- 概述: 计算页面、路由处理程序、组件、请求、类型和流,每个都有指向其列表的链接,以及要检查的内容(未解析的url、未知的触发器、类型不匹配……),每个请求一个注释。
- 地图: 路线图(图表或树)。一个页面显示了它的组件树,它在加载和事件时发送的请求(除了它的布局在每个页面上发送的请求),它通向哪里,以及它开始的流。
- 组件: 每个组件,可按名称或文件搜索,按类型(服务器/客户端/共享)过滤,按它所拥有的(发送请求,要检查的东西,不在任何页面上)排序,并按名称,请求或页面排序。
- 请求: 每个请求站点,可通过URL、组件或文件进行搜索,根据发送时间(加载、事件、服务器端、未知触发器)、状态(已验证、不匹配、需要重新检查、尚未捕获)、传输和标志进行筛选。从多个地方发送的相同请求是一行。请求在其自己的页面上打开,其中包含其主体和响应类型、服务器跳点、查询缓存链接、捕获和来自组件的调用路径。
- 流, 类型, 生活 (在它们到达时捕获它们,并进行匹配和类型检查)和 出口.
过滤器和搜索保留在URL中,因此过滤后的列表可以被添加书签或共享。随着应用增长的列表(缓存调用命中的查询,路由处理程序的调用者…)显示5个条目,然后“+N个”。
反应查询
useQuery, useInfiniteQuery悬疑的钩子, useQueries / useSuspenseQueries (每个条目一个请求), useMutation, usePrefetchQuery 和 queryClient.fetchQuery / prefetchQuery / ensureQueryData (及其无限形式)是请求站点,在v4和v5中。每个查询的键被读入段(['todos', 'detail', :id]),也通过重点工厂, queryOptions() 以及返回选项的函数。
invalidateQueries, refetchQueries, resetQueries, removeQueries 和 setQueryData 链接到他们的关键命中的查询(通过前缀或 exact)以及回调调用它们的突变,因此流显示了突变刷新的查询。一个服务器端 prefetchQuery 交给… HydrationBoundary (v4: Hydrate)链接到它所填充的客户端查询。在测试时,查询或突变发送的每个请求都携带其键,并且捕获与它匹配。
中间件
middleware.ts (和 proxy.ts 在Next.js中16)为其读取 config.matcher:每个页面显示中间件是否在其上运行。 NextResponse.redirect() 变成了路线图上的一条边缘,周围的环境(if !token), NextResponse.rewrite() 和next-intl的默认语言环境用于告诉哪个页面回答了捕获的URL,并且中间件发送的请求得到一个 middleware 触发器。在Node.js (proxy.ts)这些请求像服务器的其他部分一样被捕获;在边缘运行时,尽最大努力(参见 限制)。
tRPC
过程调用是v10和v11中的请求站点: createTRPCReact 钩子(trpc.todo.byId.useQuery())、普通客户端(client.todo.add.mutate()), utils 电话和v11 useTRPC() 与 queryOptions() / mutationOptions(). 路由器是从路由处理程序中读取的 fetchRequestHandler() (或客户的 AppRouter 类型),因此每个调用都获得其过程的输入类型(从 .input())和不带注释的输出类型。在您进行测试时,一个批处理请求被拆分为每个过程的一个捕获,每个捕获都自己进行匹配和类型检查。
邮差集合
将流检查器指向一个Postman集合(配置 postman,或者 flow-checker postman import --collection <src>),并且每次扫描都要进行检查:
- 代码中的每个请求都通过方法和URL (
{{baseUrl}}/users/:id适合`${API}/users/${id}`). 该集合没有的请求被标记 未收;收集请求没有调用被列为 未使用的端点. - 保存的响应示例为代码为none(标记为“from Postman example”)的请求提供一个类型,然后根据它检查捕获。当代码声明类型时,将根据示例(合同不匹配)。
- 扫描无法解析的URL将从其函数名和方法指向的集合请求中填充,只要其中一个正好合适。
postman import 打印摘要和调查结果列表;仪表板概述和Markdown导出(postman.md)也给他们看看。
GraphQL
Apollo Client(3和4)、urql和Relay调用是请求站点,每个操作调用一个。每个操作都指向相同的URL,因此捕获通过操作名称或持久化查询id进行匹配。批处理请求被拆分为每个操作的一个捕获,并进行类型检查比较 variables 和 data.
类型首先来自调用时的类型参数、代码根的类型化文档或Relay编译器。没有这些,设置 graphql.schema (或者让流检查器查找 schema.graphql / Relay或codegen配置中的模式),或者 graphql.introspection 要询问正在运行的端点(参见 配置). 仪表板概述列出了每个操作及其类型和调用者,以及读取它们的组件的片段。
服务器发送的事件
new EventSource(url) 和一个 fetch 作为事件流读取(Accept: text/event-stream,或者对一个路由处理程序的任何取回 text/event-stream)是请求站点。扫描列出了每个人收听的事件(onmessage, addEventListener('tick', ...)),其数据读取类型为(JSON.parse(e.data) as Tick, const t: Tick = JSON.parse(e.data), MessageEvent<Tick>),并读取路由处理程序写入的事件(event: tick / data: ${JSON.stringify(tick)},也通过一个帮手喜欢 send('tick', tick)). 事件时,代码没有接受处理程序的类型。
在您进行测试时,每个流都是逐个消息捕获的:仪表板一打开就显示捕获,然后每个消息都有其时间、事件名称和数据(像主体一样编辑),并且每个消息都根据其事件类型进行检查。 capture.maxMessages (200)和 capture.maxMessageBytes (64 kB)限制每个连接保留的内容。
尚
new WebSocket(url) 和socket . io的 io(url) 是请求站点。扫描列出了每个人发送和接收的消息及其类型: ws.send(JSON.stringify(subscribe)) 和 JSON.parse(e.data) as Quote 对于本机套接字, socket.emit('chat', draft) 和 socket.on('chat', (message) => ...) 套接字。IO类型的事件,例如 Socket<ServerEvents, ClientEvents> 两种都给)。
当您测试时,两个方向的每个消息都被捕获(像主体一样编辑),并使用封闭代码;套接字。IO事件按名称从它的帧中读取。仪表板显示发送和接收内容的时间轴,并根据其类型检查每条消息。 capture.maxMessages 和 capture.maxMessageBytes 限制每个连接保留的内容,如事件流。
详细设置
flow-checker init 显示它将改变什么,并首先询问:
instrumentation-client.ts(浏览器钩子)和instrumentation.ts(服务器钩),旁边是你的app目录(在src/当你使用src/app). 如果instrumentation.ts已经出口register(),init添加流检查器块作为它的第一个语句(见下文)。服务器钩子还注册了一个小的边缘运行时,所以从边缘运行时的中间件发送的请求(middleware.ts在Next.js 16之前)也被捕获;由0.1编写的Hooks可以从中获得它init或者doctor.flow-checker.config.ts..gitignore捕获数据的条目。
两个钩子都是 无操作,除非应用程序是由流量检查器启动的 (FLOW_CHECKER=1),并且浏览器运行时在生产构建中永远不会处于活动状态,因此提交它们是安全的。
服务器钩子首先进入 register()
两个流量检查块必须是 的第一句 register(),在任何 if, register 必须 async:
// instrumentation.ts
export async function register() {
// flow-checker: captures server-side requests while you test. It does nothing unless FLOW_CHECKER=1.
if (process.env.FLOW_CHECKER === '1' && process.env.NEXT_RUNTIME === 'nodejs') {
const { registerServerRuntime } = await import('@integr8-media/flow-checker/runtime/server')
registerServerRuntime()
}
// flow-checker: requests sent from middleware on the edge runtime (fetch only, best effort).
if (process.env.FLOW_CHECKER === '1' && process.env.NEXT_RUNTIME === 'edge') {
const { registerEdgeRuntime } = await import('@integr8-media/flow-checker/runtime/edge')
registerEdgeRuntime(process.env.FLOW_CHECKER_RUNTIME)
}
// your own code
if (process.env.NODE_ENV === 'production') {
// ...
}
}
粘贴在现有条件中(例如仅用于生产的) if)或早睡后 return,他们从不跑下去 next dev,并且服务器端请求不会被静默地捕获。
init编辑一个已有的register()对你来说:它把积木放在第一位,使registerasync需要时移动错位块时,再次运行。一个register从另一个文件重新导出后会得到一个注释,其中包含要添加的行。doctor对象中存在块时失败if,switch、循环、try三元,&&或嵌套回调,出现在其他代码之后,或当register不是async. 它命名行并打印要移动的行。dev当钩子放错位置时,在开始时发出警告;当你的应用响应路由处理程序发送请求,但在10秒内没有捕获服务器端请求时,再次发出警告。Live显示了同样的警告。
flow-checker dev 以启动dev命令 FLOW_CHECKER=1, PORT 以及环境中的捕获设置。与 --no-app,它打印该环境,这样你就可以自己启动应用程序(例如在Docker容器或单线程任务运行器中)。
配置
可选的。一切都是自动检测的。看到 文档/ config.md 对于每个选项。
// flow-checker.config.ts
import { defineConfig } from '@integr8-media/flow-checker/config'
export default defineConfig({
root: '.', // project root, relative to this file
ports: { dashboard: 4400, app: 3000 },
// Files the scan reads and skips (globs from the root). These are the defaults.
include: ['**'],
exclude: ['**/*.test.*', '**/*.spec.*', '**/*.stories.*', '**/__tests__/**', '**/__mocks__/**'],
// Packages or modules whose calls are requests: HTTP libraries and API clients the scan
// can't look inside. Default: none.
apiClients: [{ module: 'ky', methods: ['default', 'get', 'post'] }],
capture: {
storeRaw: false, // true = save captures unmasked (local debugging only)
redact: { keys: ['iban'], headers: ['x-session'] }, // added to the defaults
ignore: ['/api/health'],
},
export: { md: 'docs/flow', html: '.flow-checker/site' },
// A Postman collection to check the code against (v2.1 JSON, v3 YAML folder or { id, apiKeyEnv }).
postman: { collection: 'postman/collections/My API', environment: 'postman/environments/dev.yaml' },
// GraphQL types when the code has none: a schema file, or live introspection (env:NAME headers).
graphql: { schema: 'schema.graphql' },
})
事件流和WebSocket捕获最多保留 capture.maxMessages 每个连接有200条消息,消息结束 capture.maxMessageBytes (64 kB),而不包含他们的数据。
敏感数据
修订是 默认开启 发生在运行时,在一个捕获离开你的应用程序之前:
- 标题:
authorization,cookie,set-cookie,x-api-key,proxy-authorization. - 任意深度的主体键(不区分大小写):
password,pass,token,accessToken,refreshToken,secret,otp,code,cvv,cardNumber,ssn. - 类似jwt的值、承载令牌或卡号,以及敏感的查询参数。
遮罩值保持其形状("[REDACTED:string]", 0, false),所以类型检查仍然有效。工具服务器在到达时再次编辑,并再次导出编辑,除非您通过 --raw.
文件
.flow-checker/
├─ model.json scan result (safe to commit)
├─ overrides.json your decisions: manual matches, flow names, recordings (safe to commit)
├─ captures/ captured requests (gitignored)
├─ sessions/ browsing sessions used to confirm flows (gitignored)
├─ cache/ ingest token, code hashes, GraphQL introspection (gitignored)
└─ postman/ the last imported Postman collection (gitignored)
限制
- 仅限应用路由器。
- WebSockets:仅在浏览器中(不包括在服务器上打开的socket)。在模块级别(在它调用的组件或函数之外)创建的套接字不是请求站点。套接字。在连接移动到WebSocket之前,通过HTTP长轮询发送的消息不会被捕获(使用
transports: ['websocket']为了捕获所有内容,不记录ack,二进制附件不保留数据,并且在匹配时不区分名称空间。 - 服务器发送的事件:EventSource只报告有人侦听的命名事件,因此应用程序没有侦听器的事件不会被记录。等库读取的事件流
@microsoft/fetch-event-source被捕获(他们使用?fetch),但他们的听众不会被扫描出来。边缘运行时上的流被捕获为普通请求。 - GraphQL: WebSockets上的订阅被捕获为WebSocket消息,但不链接到它们的操作。通过你自己的行动
fetchwrapper(不是Apollo、urql或Relay)是普通请求。服务器解析器不链接到运行它们的操作。 - Edge运行时(Next.js 16之前的中间件):捕获是最好的努力,
fetch只有和没有传入请求。Node.js上的中间件(proxy.ts,或者runtime: 'nodejs')像服务器的其他部分一样被捕获。 - 中间件
config.matcher必须在文件中写入(如Next.js所要求的);路由与动态段的样例值相匹配,对运行时构建的url的重定向或重写显示为写入的内容。 - tRPC:通过调用的程序
createCaller或者服务器端createTRPCOptionsProxy({ router })在没有HTTP请求的情况下运行,并且不是请求站点。在代码中可以找到订阅,但它们的流被捕获为普通请求。路由器必须是可达的fetchRequestHandler({ router })或者是客户的AppRouter类型。 - 15.3之前的Next.js:浏览器捕获需要webpack(没有turbpack)和
withFlowChecker(). - 大型存储库:第一次扫描约1800个文件的应用程序大约需要12秒,编辑后重新扫描大约需要4秒。重新扫描在工作线程中运行,因此仪表板和捕获同时保持响应。
许可证
麻省理工学院