mcpField
N
nostics

nostics

github.com/vercel-labs开发24056 次安装4 周前更新
代码仓库

关于 nostics

nostics 是用于 TypeScript 项目的结构化诊断库,面向需要为错误提供稳定代码、修复建议和文档链接的开发者。它支持报告器、序列化,并可在生产构建中移除仅用于报告的诊断。

主要功能

  1. 01定义带类型的诊断代码
  2. 02附加修复建议与文档链接
  3. 03通过报告器输出诊断
  4. 04序列化诊断以跨进程传递
  5. 05提供格式化器与 Vite 构建插件

适用场景

  1. 01为构建和运行时错误建立稳定代码
  2. 02将诊断记录发送到文件或 URL
  3. 03在 Vite 开发期间收集诊断
  4. 04从生产包中移除仅报告的诊断

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/vercel-labs/nostics --skill nostics

安装后在 Claude Code 中输入/nostics确认已加载。

附带文件references/documentation-site.mdreferences/migration.md

详细介绍

nostics

每种错误情况都会转化为带有稳定代码、文档 URL 和可执行修复方案 fix 的类型化 Diagnostic(继承 Error)。可通过 toJSON() 序列化。

Diagnostic:name(代码)、message/why(插值文本)、fix?、docs?、sources?('file:line:column')、cause?、toJSON()。可以抛出它,使用 instanceof Diagnostic 捕获,并通过 toJSON() 跨进程边界传递。

defineDiagnostics

每个代码都会返回一个可调用句柄。调用句柄会创建一个新的 Diagnostic,按顺序触发所有报告器,并返回该对象。可以 throw 返回值来抛出错误(报告器仍会运行,因此抛出的诊断也会被报告)。

import { createConsoleReporter, defineDiagnostics } from 'nostics'

const diagnostics = /*#__PURE__*/ defineDiagnostics({
  docsBase: (code) => `https://nuxt.com/e/${code.replace('NUXT_', '').toLowerCase()}`,
  reporters: [/*#__PURE__*/ createConsoleReporter()],
  codes: {
    NUXT_B1001: {
      why: 'Could not compile template.',
      fix: 'Check the template for syntax errors.',
    },
    NUXT_B2011: {
      why: (p: { src: string }) => `Invalid plugin "${p.src}". src option is required.`,
      fix: 'Pass a string path or an object with a `src` to `addPlugin()`.',
    },
    NUXT_W9001: { why: 'message', docs: false }, // per-code: string overrides docsBase, false opts out
  },
})
  • docsBase string | (code) => string | undefined:字符串会追加 /${code.toLowerCase()};函数会返回完整 URL(或返回 undefined 以省略)。
  • codes:每个定义都需要 why(string | (params) => string,唯一必填字段,会成为 Error.message);fix(string | (params) => string)、docs(string | false)和 data(object | (params) => object)为可选。每个代码解析后的 data 都有类型,并会包含在 toJSON() 中。
  • reporters:每次调用都会触发;可选。它们的 options 类型会取交集;报告器要求的选项在调用处也会成为必填项。对于代码仅会被 throw 的目录,请省略此项(或传入 []):抛出的 Diagnostic 已经包含消息,因此控制台报告器会先打印一次,而未捕获的错误又会显示一次,造成明显重复。如果一类警告仅需报告,而另一类错误需要抛出,请将它们放在不同的目录中,这样前者可以使用报告器,而后者不需要。
  • 参数推断:why、fix 和 data 中的参数类型会取交集,并在调用处变为必填。如果 why 需要 { src },而 fix 需要 { date },则调用时必须传入 { src, date }。

调用位置

diagnostics.NUXT_B1001() // no params: report only
diagnostics.NUXT_B2011({ src: '/plugins/bad.ts' }) // params first
diagnostics.NUXT_B2011({
  src,
  cause: originalError,
  sources: ['nuxt.config.ts:42:3'],
}) // runtime fields merge in
diagnostics.NUXT_B2011({ src }, { method: 'error' }) // reporter options second
throw diagnostics.NUXT_B2011({ src }) // raise

cause/sources 放在参数对象中;对于构建/配置诊断,sources 尤其重要,因为 JS 堆栈会指向库内部。使用 if (err instanceof Diagnostic) 捕获,然后读取 .name、.message、.fix、.docs 和 .data。

defineProdDiagnostics() 中不包含定义数据,其 .data 为 undefined。因此,开发/生产环境的三元表达式会将 .data 的类型推断为代码的数据类型或 undefined。

报告器

(diagnostic: Diagnostic, options?: Opts) => void。声明必填的 options 类型后,调用处的第二个参数也会变为必填,并具有对应类型。

报告器导入描述
createConsoleReporter(options?)nosticsconsole[method](formatter(d))。method 默认为 'warn'('log'|'warn'|'error'),formatter 默认为 formatDiagnostic;两者都通过选项设置,且 method 也可在每次调用时覆盖。
createFetchReporter(url)nostics/reporters/fetch将诊断 JSON 发送到该 URL;发送失败会被忽略。
createFileReporter(options?)nostics/reporters/node将 NDJSON 追加到本地文件(默认为 .nostics.log)。
createDevReporter()nostics/reporters/dev通过 import.meta.hot.send() 将 toJSON() 发送到 Vite 开发服务器。
import type { DiagnosticReporter } from 'nostics'
const sentryReporter: DiagnosticReporter = (d) =>
  sentry.captureMessage(d.message, { tags: { code: d.name } })
const audited: DiagnosticReporter<{ priority: number }> = (d, o) =>
  audit.log({ name: d.name, priority: o.priority })
// → audited makes diagnostics.X({...}, { priority: 1 }) required and type-checked.

格式化器

格式化器导入描述
formatDiagnosticnostics带 Unicode 装饰的纯文本字符串(内置报告器会使用它)。
ansiFormatter(colors)nostics/formatters/ansi彩色输出;接受 Colors 接口(red/yellow/cyan/gray/bold/dim,每个都是 (s) => string)。
jsonFormatternostics/formatters/json通过 toJSON() 执行 JSON.stringify(diagnostic)。

formatDiagnostic 输出的详情顺序固定为 fix → sources → see,缺失的字段会省略:

[NUXT_B2011] Invalid plugin `/plugins/bad.ts`. src option is required.
├▶ fix: Pass a string path or an object with a `src` to `addPlugin()`.
├▶ sources: nuxt.config.ts:42:3
╰▶ see: https://nuxt.com/e/b2011

Vite 插件(@nostics/unplugin,开发依赖)

@nostics/unplugin/strip-transform(库作者使用,优化构建)和 @nostics/unplugin/dev-server-collector(应用开发者使用,在开发时收集)。两者都基于 unplugin:.vite()、.webpack()、.rollup() 等。

  • nosticsStrip 会为 defineDiagnostics() 标记 /*#__PURE__*/,并为裸的诊断表达式语句添加 NODE_ENV 守卫,使它们在生产环境中能被摇树优化移除。选项 packageName?(默认为 'nostics')。抛出、返回和赋值会保留(它们属于行为)。为便于追踪:使用相对导入,直接导出目录,不要使用工厂包装或深层聚合导出。
    • 该插件是可选的。如果目录已手动添加注释,即使没有构建转换,也会产生相同的生产输出:在 defineDiagnostics( 前以及其中的每个报告器工厂调用前添加 /*#__PURE__*/(如本文所有示例所示),并为每个仅用于报告的调用点添加开发环境守卫(process.env.NODE_ENV !== 'production' && diagnostics.CODE(p))。始终在源代码中添加这些注释;如果仅用于报告的调用点没有守卫,而你又想在不修改这些调用点的情况下移除它们,则使用该插件。
  • nosticsCollector 通过 Vite WebSocket 监听 createDevReporter() 诊断,并通过 createFileReporter 将其写为 NDJSON。仅适用于 Vite。选项 logFile?(默认为 .nostics.log)、debug?(默认为 !!process.env.DEBUG)。
// vite.config.ts
import { nosticsStrip } from '@nostics/unplugin/strip-transform'
import { nosticsCollector } from '@nostics/unplugin/dev-server-collector'
export default defineConfig({
  plugins: [nosticsStrip.vite(), nosticsCollector.vite()],
})

// src/diagnostics.ts — pair the collector with createDevReporter()
import { createConsoleReporter, defineDiagnostics } from 'nostics'
import { createDevReporter } from 'nostics/reporters/dev'
export const diagnostics = /*#__PURE__*/ defineDiagnostics({
  reporters: [/*#__PURE__*/ createConsoleReporter(), /*#__PURE__*/ createDevReporter()],
  codes: {/* ... */},
})

生产构建

  • 仅用于报告的诊断(裸的 diagnostics.X())应被移除:nosticsStrip 或手动添加的注释会移除它们,随后未使用的目录会被摇树优化。
  • 仍然存在的诊断(throw/return/赋值/参数)会保留,并且每个都会使整个目录保持可访问,因此所有 why/fix 内容都会被打包。并非每个库都会在生产环境中抛出异常:如果你的库只报告诊断,移除这些诊断就够了,到此为止。

如果库确实会在生产环境中执行 throw,请在定义时使用带 NODE_ENV 三元表达式的 defineProdDiagnostics,以便使用者的打包工具移除开发环境分支(所有目录文本):

import { defineDiagnostics, defineProdDiagnostics } from 'nostics'
export const diagnostics =
  process.env.NODE_ENV === 'production'
    ? /*#__PURE__*/ defineProdDiagnostics({ docsBase })
    : /*#__PURE__*/ defineDiagnostics({
        docsBase,
        reporters: [/* ... */],
        codes: {/* text */},
      })

访问到的代码会成为实例的 name,docs 仍从 docsBase 派生;如果存在文档 URL,why 会指向该 URL(否则为空),不会打包 why/fix 文本。默认不包含 reporters(因此仍然存在的 throw 不会同时记录日志,然后又作为未捕获错误重新出现);传入 reporters 可保留生产环境遥测。nosticsStrip 会像追踪直接导出的目录一样追踪这个三元表达式。

约定

  • 代码是稳定且完全限定的 PREFIX_XNNNN(B 表示构建,R 表示运行时,C 表示配置,D 表示弃用)。已发布的代码绝不可重复使用或重新分配。
  • 在测试中,断言诊断代码,而不是其消息或格式化文本。检查抛出的 Diagnostic 的 .name,或检查报告器输出中的 [CODE]。
  • 始终提供 why;只要已知解决方案,就提供 fix(这是对人类和 Agent 最有用的字段)。运行时值应使用参数化模板,而不是在工厂外进行字符串拼接。
  • why 用于诊断,fix 用于补救——应拆分二者,避免内容重叠。 why 只说明问题所在;fix 只说明要采取的措施。报告器会同时打印两者,因此重复出现的措辞毫无意义。当一个源句同时包含两者("A hash must start with '#'. Prefix it with '#'.")时,应将其拆成两部分——诊断放入 why,补救措施放入 fix——而不是把整句放进 why,再在 fix 中重复。fix 也接受参数函数((p) => ...),因此应将包含值的补救措施(use "#${p.hash}")移入其中,而不是留在 why 中。
  • 重新抛出时传递 cause;当 JS 堆栈无法反映用户的源代码时,传递 sources。
  • 按领域拆分大型目录(diagnostics/build.ts、runtime.ts、config.ts),并从 index.ts 重新导出;每个 defineDiagnostics() 共享 docsBase,且各自使用独立的代码范围。

参考资料

  • 将现有库迁移到 nostics(以诊断代码替换 console.warn/console.error/warn()/抛出的 Error,且不改变运行时行为):从头到尾遵循 references/migration.md。
  • 构建错误代码文档站点(页面模板、部署、Agent 优化):references/documentation-site.md。

评论

支持 Markdown