Skip to content

Commit 9bd928d

Browse files
antfuclaude
andauthored
feat: migrate warnings/errors to structured diagnostics via logs-sdk (#285)
* feat: migrate warnings/errors to structured diagnostics via logs-sdk Replace inconsistent console.warn/error/throw patterns with structured diagnostics using @antfu/experimental-logs-sdk. Each error gets a unique code (DTK0001-DTK0032 for core/rpc, RDDT0001-RDDT0002 for rolldown) with auto-generated docs URLs and ANSI-formatted output. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add throw keyword before logger.throw() for TypeScript control flow TypeScript doesn't narrow types after `logger.CODE().throw()` because the return type inference doesn't propagate `never` through the chained call. Adding `throw` before the expression fixes control flow analysis. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: migrate from @antfu/experimental-logs-sdk to logs-sdk@0.0.5 Package renamed from `@antfu/experimental-logs-sdk` to `logs-sdk` (now under vercel-labs/logs-sdk). Updated all imports, catalog entry, and docs references. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add structured diagnostics guide to AGENTS.md Document the error code system (prefixes, numbering, diagnostics files) and step-by-step instructions for adding new errors so future agents follow the established pattern. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: upgrade logs-sdk to 0.0.6 Rename `reporter` to `reporters` in createLogger options to match the updated API. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent be81f13 commit 9bd928d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+2795
-53
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,6 @@ packages/kit/skills
2020
docs/.vitepress/cache
2121
.turbo
2222
.context
23+
24+
# Agent skills from npm packages (managed by skills-npm)
25+
**/skills/npm-*

AGENTS.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,73 @@ pnpm -C docs run docs # docs dev server
6262
- Shared UI components/preset in `packages/ui`; use `presetDevToolsUI` from `@vitejs/devtools-ui/unocss`.
6363
- Currently focused on Rolldown build-mode analysis; dev-mode support is deferred.
6464

65+
## Structured Diagnostics (Error Codes)
66+
67+
All node-side warnings and errors use structured diagnostics via [`logs-sdk`](https://github.com/vercel-labs/logs-sdk). Never use raw `console.warn`, `console.error`, or `throw new Error` with ad-hoc messages in node-side code — always define a coded diagnostic.
68+
69+
### Code prefixes
70+
71+
| Prefix | Package(s) | Diagnostics file |
72+
|--------|-----------|-----------------|
73+
| `DTK` | `packages/rpc`, `packages/core` | `packages/rpc/src/diagnostics.ts`, `packages/core/src/node/diagnostics.ts` |
74+
| `RDDT` | `packages/rolldown` | `packages/rolldown/src/node/diagnostics.ts` |
75+
| `VDT` | `packages/vite` (reserved) ||
76+
77+
Codes are sequential 4-digit numbers per prefix (e.g. `DTK0033`, `RDDT0003`). Check the existing diagnostics file to find the next available number.
78+
79+
### Adding a new error
80+
81+
1. **Define the code** in the appropriate `diagnostics.ts`:
82+
```txt
83+
// diagnostics.ts
84+
DTK0033: {
85+
message: (p: { name: string }) => `Something went wrong with "${p.name}"`,
86+
hint: 'Optional hint for the user.',
87+
level: 'warn', // defaults to 'error' if omitted
88+
},
89+
```
90+
91+
2. **Use the logger** at the call site:
92+
```ts
93+
import { logger } from './diagnostics'
94+
95+
// For thrown errors — always prefix with `throw` for TypeScript control flow:
96+
throw logger.DTK0033({ name }).throw()
97+
98+
// For logged warnings/errors (not thrown):
99+
logger.DTK0033({ name }).log() // uses definition level
100+
logger.DTK0033({ name }).warn() // override to warn
101+
logger.DTK0033({ name }, { cause: error }).log() // attach cause
102+
```
103+
104+
3. **Create a docs page** at `docs/errors/DTK0033.md`:
105+
```md
106+
---
107+
outline: deep
108+
---
109+
# DTK0033: Short Title
110+
> Package: `@vitejs/devtools`
111+
## Message
112+
> Something went wrong with "`{name}`"
113+
## Cause
114+
When and why this occurs.
115+
## Example
116+
Code that triggers it.
117+
## Fix
118+
How to resolve it.
119+
## Source
120+
`packages/core/src/node/filename.ts`
121+
```
122+
123+
4. **Update the index** at `docs/errors/index.md` — add a row to the table.
124+
125+
5. **Update the sidebar** in `docs/.vitepress/config.ts` — the DTK items are auto-generated from `Array.from({ length: N })`, so increment the length. RDDT items are listed manually.
126+
127+
### Scope
128+
129+
- **Node-side only**: `packages/rpc`, `packages/core/src/node`, `packages/rolldown/src/node`.
130+
- **Client-side excluded**: Vue components, webcomponents, and browser-only code keep using `console.*` / `throw`.
131+
65132
## Before PRs
66133

67134
```sh

docs/.vitepress/config.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,29 @@ export default extendConfig(withMermaid(defineConfig({
9191
{ text: 'Examples', link: '/kit/examples' },
9292
],
9393
},
94+
{
95+
text: 'Error Reference',
96+
link: '/errors/',
97+
collapsed: true,
98+
items: [
99+
{
100+
text: 'DevTools Kit (DTK)',
101+
collapsed: true,
102+
items: Array.from({ length: 32 }, (_, i) => {
103+
const code = `DTK${String(i + 1).padStart(4, '0')}`
104+
return { text: code, link: `/errors/${code}` }
105+
}),
106+
},
107+
{
108+
text: 'Rolldown DevTools (RDDT)',
109+
collapsed: true,
110+
items: [
111+
{ text: 'RDDT0001', link: '/errors/RDDT0001' },
112+
{ text: 'RDDT0002', link: '/errors/RDDT0002' },
113+
],
114+
},
115+
],
116+
},
94117
],
95118

96119
search: {

docs/errors/DTK0001.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# DTK0001: RPC Function Already Registered
6+
7+
> Package: `@vitejs/devtools-rpc`
8+
9+
## Message
10+
11+
> RPC function "`{name}`" is already registered
12+
13+
## Cause
14+
15+
This error is thrown by `RpcFunctionsCollectorBase.register()` when you attempt to register an RPC function with a name that already exists in the collector. Each function name must be unique within a collector instance unless you explicitly opt into overwriting.
16+
17+
## Example
18+
19+
```ts
20+
import { defineRpcFunction } from '@vitejs/devtools-kit'
21+
22+
const getVersion = defineRpcFunction({
23+
name: 'my-plugin:get-version',
24+
type: 'static',
25+
handler: () => '1.0.0',
26+
})
27+
28+
const getVersionAlt = defineRpcFunction({
29+
name: 'my-plugin:get-version', // same name as above
30+
type: 'static',
31+
handler: () => '2.0.0',
32+
})
33+
34+
// First registration succeeds
35+
collector.register(getVersion)
36+
37+
// Second registration throws DTK0001
38+
collector.register(getVersionAlt)
39+
```
40+
41+
## Fix
42+
43+
Either use a unique name for each function, or pass `force: true` to overwrite the existing registration:
44+
45+
```ts
46+
// Option 1: Use a unique name
47+
const getVersionAlt = defineRpcFunction({
48+
name: 'my-plugin:get-version-alt',
49+
type: 'static',
50+
handler: () => '2.0.0',
51+
})
52+
collector.register(getVersionAlt)
53+
54+
// Option 2: Force overwrite
55+
collector.register(getVersionAlt, true)
56+
```
57+
58+
## Source
59+
60+
`packages/rpc/src/collector.ts`

docs/errors/DTK0002.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# DTK0002: RPC Function Not Registered (Update)
6+
7+
> Package: `@vitejs/devtools-rpc`
8+
9+
## Message
10+
11+
> RPC function "`{name}`" is not registered. Use register() to add new functions.
12+
13+
## Cause
14+
15+
This error is thrown by `RpcFunctionsCollectorBase.update()` when you attempt to update a function definition that has not been registered yet. The `update()` method is intended for replacing an existing definition, not for adding new ones.
16+
17+
## Example
18+
19+
```ts
20+
import { defineRpcFunction } from '@vitejs/devtools-kit'
21+
22+
const getVersion = defineRpcFunction({
23+
name: 'my-plugin:get-version',
24+
type: 'static',
25+
handler: () => '2.0.0',
26+
})
27+
28+
// Throws DTK0002 because 'my-plugin:get-version' was never registered
29+
collector.update(getVersion)
30+
```
31+
32+
## Fix
33+
34+
Register the function first with `register()`, then use `update()` for subsequent changes. Alternatively, pass `force: true` to `update()` to allow upserting:
35+
36+
```ts
37+
// Option 1: Register first, update later
38+
collector.register(getVersion)
39+
// ... later ...
40+
collector.update(getVersionUpdated)
41+
42+
// Option 2: Force update (creates if missing)
43+
collector.update(getVersion, true)
44+
```
45+
46+
## Source
47+
48+
`packages/rpc/src/collector.ts`

docs/errors/DTK0003.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# DTK0003: RPC Function Schema Not Found
6+
7+
> Package: `@vitejs/devtools-rpc`
8+
9+
## Message
10+
11+
> RPC function "`{name}`" is not registered
12+
13+
## Cause
14+
15+
This error is thrown by `RpcFunctionsCollectorBase.getSchema()` when you request the argument and return schemas for a function name that does not exist in the collector. The function must be registered before its schema can be queried.
16+
17+
## Example
18+
19+
```ts
20+
// Throws DTK0003 because 'my-plugin:get-config' was never registered
21+
const schema = collector.getSchema('my-plugin:get-config')
22+
```
23+
24+
## Fix
25+
26+
Ensure the function is registered before querying its schema. You can check whether a function exists first using `has()`:
27+
28+
```ts
29+
import { defineRpcFunction } from '@vitejs/devtools-kit'
30+
31+
const getConfig = defineRpcFunction({
32+
name: 'my-plugin:get-config',
33+
type: 'query',
34+
handler: () => ({ debug: false }),
35+
})
36+
37+
// Register first
38+
collector.register(getConfig)
39+
40+
// Now getSchema works
41+
const schema = collector.getSchema('my-plugin:get-config')
42+
43+
// Or guard with has()
44+
if (collector.has('my-plugin:get-config')) {
45+
const schema = collector.getSchema('my-plugin:get-config')
46+
}
47+
```
48+
49+
## Source
50+
51+
`packages/rpc/src/collector.ts`

docs/errors/DTK0004.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# DTK0004: Missing RPC Handler
6+
7+
> Package: `@vitejs/devtools-rpc`
8+
9+
## Message
10+
11+
> Either handler or setup function must be provided for RPC function "`{name}`"
12+
13+
## Cause
14+
15+
This error is thrown by `getRpcHandler()` when an RPC function definition provides neither a `handler` property nor a `setup` function that returns a `handler`. It can occur in two situations:
16+
17+
1. When the collector's proxy tries to resolve a handler for a registered function.
18+
2. During `dumpFunctions()` when resolving handlers for pre-computation.
19+
20+
Every RPC function must have a way to produce a handler -- either directly via `handler` or lazily via `setup`.
21+
22+
## Example
23+
24+
```ts
25+
import { defineRpcFunction } from '@vitejs/devtools-kit'
26+
27+
// Missing both handler and setup
28+
const broken = defineRpcFunction({
29+
name: 'my-plugin:broken',
30+
type: 'query',
31+
})
32+
33+
collector.register(broken)
34+
35+
// Throws DTK0004 when the handler is resolved
36+
await collector.getHandler('my-plugin:broken')
37+
```
38+
39+
A `setup` function that forgets to return a handler also triggers this error:
40+
41+
```ts
42+
const alsoMissing = defineRpcFunction({
43+
name: 'my-plugin:also-missing',
44+
type: 'query',
45+
setup: (ctx) => {
46+
// Forgot to return { handler: ... }
47+
return {}
48+
},
49+
})
50+
```
51+
52+
## Fix
53+
54+
Provide a `handler` directly, or return one from `setup`:
55+
56+
```ts
57+
// Option 1: Direct handler
58+
const getVersion = defineRpcFunction({
59+
name: 'my-plugin:get-version',
60+
type: 'static',
61+
handler: () => '1.0.0',
62+
})
63+
64+
// Option 2: Handler via setup (useful when you need context)
65+
const getConfig = defineRpcFunction({
66+
name: 'my-plugin:get-config',
67+
type: 'query',
68+
setup: ctx => ({
69+
handler: () => ctx.config,
70+
}),
71+
})
72+
```
73+
74+
## Source
75+
76+
`packages/rpc/src/handler.ts`

0 commit comments

Comments
 (0)