Skip to content

Commit d1bc706

Browse files
authored
feat!: migrate logs-sdk to nostics, bump devframe to v0.3 (#350)
1 parent dd41331 commit d1bc706

27 files changed

Lines changed: 529 additions & 255 deletions

AGENTS.md

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ The kit is the integration hub. When adding to it, the question is "does this he
9393

9494
## Structured Diagnostics (Error Codes)
9595

96-
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.
96+
All node-side warnings and errors use structured diagnostics via [`nostics`](https://github.com/vercel-labs/nostics). Never use raw `console.warn`, `console.error`, or `throw new Error` with ad-hoc messages in node-side code — always define a coded diagnostic.
9797

9898
### Code prefixes
9999

@@ -115,23 +115,21 @@ Codes are sequential 4-digit numbers per prefix (e.g. `DTK0033`, `RDDT0003`). Ch
115115
```txt
116116
// diagnostics.ts
117117
DTK0033: {
118-
message: (p: { name: string }) => `Something went wrong with "${p.name}"`,
119-
hint: 'Optional hint for the user.',
120-
level: 'warn', // defaults to 'error' if omitted
118+
why: (p: { name: string }) => `Something went wrong with "${p.name}"`,
119+
fix: 'Optional remediation hint for the user.',
121120
},
122121
```
123122

124-
2. **Use the logger** at the call site:
123+
2. **Emit the diagnostic** at the call site:
125124
```ts
126-
import { logger } from './diagnostics'
125+
import { diagnostics } from './diagnostics'
127126

128127
// For thrown errors — always prefix with `throw` for TypeScript control flow:
129-
throw logger.DTK0033({ name }).throw()
128+
throw diagnostics.DTK0033.throw({ name })
130129

131-
// For logged warnings/errors (not thrown):
132-
logger.DTK0033({ name }).log() // uses definition level
133-
logger.DTK0033({ name }).warn() // override to warn
134-
logger.DTK0033({ name }, { cause: error }).log() // attach cause
130+
// For reported (non-thrown) diagnostics:
131+
diagnostics.DTK0033.report({ name })
132+
diagnostics.DTK0033.report({ name, cause: error }) // attach cause via params
135133
```
136134

137135
3. **Create a docs page** at `docs/errors/DTK0033.md`:

devframe

Submodule devframe updated 51 files

docs/errors/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Vite DevTools uses structured diagnostics to surface actionable warnings and err
1111
- Codes follow the pattern **prefix + 4-digit number** (e.g., `DF0001`, `DTK0008`, `RDDT0002`).
1212
- Each prefix maps to a package: `DTK` for `@vitejs/devtools` (Vite-specific pieces), `RDDT` for `@vitejs/devtools-rolldown`. The framework-neutral `devframe` package documents its own `DF`-prefixed codes at the [Devframe docs site](https://devfra.me/errors/).
1313
- Every error page includes the cause, recommended fix, and a reference to the source file that emits it.
14-
- The diagnostics system is powered by [`logs-sdk`](https://github.com/vercel-labs/logs-sdk), which provides structured logging with docs URLs, ANSI-formatted console output, and level-based filtering.
14+
- The diagnostics system is powered by [`nostics`](https://github.com/vercel-labs/nostics), which provides structured diagnostic codes with docs URLs and ANSI-formatted console output.
1515

1616
## DevTools Kit (DTK)
1717

docs/kit/diagnostics.md

Lines changed: 31 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Structured Diagnostics
22

3-
`ctx.diagnostics` is a thin layer over [`logs-sdk`](https://github.com/vercel-labs/logs-sdk) that lets DevTools plugins register coded errors and warnings into a shared logger without depending on `logs-sdk` directly. Use it for author-defined coded diagnostics — errors, warnings, deprecations — that carry a stable code, a documentation URL, and a structured payload. For free-form runtime output that should appear in the DevTools UI, use [`ctx.messages`](./messages).
3+
`ctx.diagnostics` is a thin layer over [`nostics`](https://github.com/vercel-labs/nostics) that lets DevTools plugins register coded errors and warnings into a shared registry without depending on `nostics` directly. Use it for author-defined coded diagnostics — errors, warnings, deprecations — that carry a stable code, a documentation URL, and a structured payload. For free-form runtime output that should appear in the DevTools UI, use [`ctx.messages`](./messages).
44

55
| Surface | Purpose | Example |
66
|---------|---------|---------|
@@ -11,17 +11,20 @@
1111

1212
```ts
1313
interface DevToolsDiagnosticsHost {
14-
/** Combined logs-sdk Logger across all registered diagnostics. */
15-
readonly logger: Logger
14+
/**
15+
* Proxy-backed lookup of every registered code by name. Each entry is a
16+
* `nostics` handle with `.report()` and `.throw()` methods.
17+
*/
18+
readonly logger: Record<string, any>
1619

1720
/** Register additional diagnostic definitions. */
18-
register: (definitions: DiagnosticsResult) => void
21+
register: (definitions: Record<string, unknown>) => void
1922

20-
/** Re-export of logs-sdk's `defineDiagnostics`. */
23+
/**
24+
* Mirror of `nostics`'s `defineDiagnostics`, pre-wired with the host's
25+
* ANSI console reporter — plugins typically omit `reporters`.
26+
*/
2127
defineDiagnostics: typeof defineDiagnostics
22-
23-
/** Re-export of logs-sdk's `createLogger`. */
24-
createLogger: typeof createLogger
2528
}
2629
```
2730

@@ -41,20 +44,19 @@ export function MyPlugin(): PluginWithDevTools {
4144
docsBase: 'https://example.com/errors',
4245
codes: {
4346
MYP0001: {
44-
message: (p: { name: string }) => `Plugin "${p.name}" is not configured`,
45-
hint: 'Add the plugin to your `vite.config.ts` and pass an options object.',
47+
why: (p: { name: string }) => `Plugin "${p.name}" is not configured`,
48+
fix: 'Add the plugin to your `vite.config.ts` and pass an options object.',
4649
},
4750
MYP0002: {
48-
message: 'Cache directory missing — running cold.',
49-
level: 'warn',
51+
why: 'Cache directory missing — running cold.',
5052
},
5153
},
5254
})
5355

5456
ctx.diagnostics.register(diagnostics)
5557

56-
// Now you can emit codes through the shared logger:
57-
ctx.diagnostics.logger.MYP0002().log()
58+
// Emit codes through the shared lookup:
59+
ctx.diagnostics.logger.MYP0002.report()
5860
},
5961
},
6062
}
@@ -74,68 +76,49 @@ Prefixes used by the in-tree packages:
7476
| `RDDT` | `@vitejs/devtools-rolldown` |
7577
| `VDT` | `@vitejs/devtools-vite` (reserved) |
7678

77-
Each definition supports `message` (string or function), optional `hint`, optional `level` (`'error'` / `'warn'` / `'suggestion'` / `'deprecation'` — defaults to `'error'`), and a `docsBase` for generating documentation URLs.
79+
Each definition supports `why` (string or function returning a string) and an optional `fix` (string or function). A `docsBase` on the definition group auto-attaches a per-code URL to every emitted diagnostic.
7880

7981
## Emit a diagnostic
8082

81-
Each registered code becomes a callable factory on `ctx.diagnostics.logger`. The factory returns an object with `.throw()`, `.warn()`, `.error()`, `.log()`, and `.format()`.
83+
Each registered code is reachable as a property on `ctx.diagnostics.logger`. Every handle exposes `.throw(params)` and `.report(params)`.
8284

8385
```ts
8486
// Throw — control flow stops here
85-
throw ctx.diagnostics.logger.MYP0001({ name: 'foo' }).throw()
86-
87-
// Log without throwing
88-
ctx.diagnostics.logger.MYP0002().log()
87+
throw ctx.diagnostics.logger.MYP0001.throw({ name: 'foo' })
8988

90-
// Override level per call
91-
ctx.diagnostics.logger.MYP0002().warn()
89+
// Report without throwing (goes through the host's reporter)
90+
ctx.diagnostics.logger.MYP0002.report()
9291

93-
// Attach a `cause`
94-
ctx.diagnostics.logger.MYP0001({ name: 'foo' }, { cause: error }).log()
92+
// Attach a `cause` via the params object
93+
ctx.diagnostics.logger.MYP0001.report({ name: 'foo', cause: error })
9594
```
9695

9796
`.throw()` is typed `never`. Prefix the call with `throw` so TypeScript narrows control flow correctly:
9897

9998
```ts
100-
throw ctx.diagnostics.logger.MYP0001({ name }).throw()
99+
throw ctx.diagnostics.logger.MYP0001.throw({ name })
101100
```
102101

103-
## Typed logger reference
102+
## Typed handle reference
104103

105-
`ctx.diagnostics.logger` is loosely typed — it covers an unbounded set of registered codes, beyond what TypeScript can narrow. For autocompletion on your plugin's specific codes, keep a typed reference returned from `createLogger`:
104+
`ctx.diagnostics.logger` is a loosely typed proxy — it covers an unbounded set of registered codes, beyond what TypeScript can narrow. For autocompletion on your plugin's specific codes, keep a reference to the typed handle returned by `defineDiagnostics()`:
106105

107106
```ts
108107
const myDiagnostics = ctx.diagnostics.defineDiagnostics({
109108
docsBase: 'https://example.com/errors',
110109
codes: {
111-
MYP0001: { message: (p: { name: string }) => `…${p.name}` },
110+
MYP0001: { why: (p: { name: string }) => `…${p.name}` },
112111
},
113112
})
114113

115-
// Register so the shared logger can also see it
114+
// Register so the shared lookup can also see it
116115
ctx.diagnostics.register(myDiagnostics)
117116

118-
// Keep a typed reference for your own emit sites
119-
const logger = ctx.diagnostics.createLogger({ diagnostics: [myDiagnostics] })
120-
logger.MYP0001({ name: 'foo' }).warn()
121-
```
122-
123-
Both loggers share the formatter and reporter defaults set by the host (ANSI console output).
124-
125-
## Don't cache the combined logger
126-
127-
`ctx.diagnostics.logger` is a getter — it returns the freshest combined logger, rebuilt each time `register()` is called. Don't cache it across registrations:
128-
129-
```ts
130-
// ❌ Stale after a later register() call
131-
const log = ctx.diagnostics.logger
132-
log.MYP0001({ name: 'foo' }).log()
133-
134-
// ✅ Always fresh
135-
ctx.diagnostics.logger.MYP0001({ name: 'foo' }).log()
117+
// Use the typed handle directly at emit sites
118+
myDiagnostics.MYP0001.report({ name: 'foo' })
136119
```
137120

138-
For a stable reference, use the typed `createLogger` form above.
121+
Both paths share the formatter and reporter defaults set by the host (ANSI console output).
139122

140123
## Document your codes
141124

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@
6161
"chokidar": "catalog:devtools",
6262
"esbuild": "catalog:build",
6363
"eslint": "catalog:devtools",
64-
"logs-sdk": "catalog:deps",
6564
"magic-string": "catalog:build",
6665
"nano-staged": "catalog:devtools",
66+
"nostics": "catalog:deps",
6767
"nuxt": "catalog:build",
6868
"p-limit": "catalog:deps",
6969
"pathe": "catalog:deps",

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@
6262
"cac": "catalog:deps",
6363
"devframe": "catalog:deps",
6464
"h3": "catalog:deps",
65-
"logs-sdk": "catalog:deps",
6665
"mlly": "catalog:deps",
66+
"nostics": "catalog:deps",
6767
"obug": "catalog:deps",
6868
"pathe": "catalog:deps",
6969
"perfect-debounce": "catalog:deps",

packages/core/src/node/cli-commands.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { colors as c } from 'devframe/utils/colors'
88
import { open } from 'devframe/utils/open'
99
import { resolve } from 'pathe'
1010
import { MARK_NODE } from './constants'
11-
import { logger } from './diagnostics'
11+
import { diagnostics } from './diagnostics'
1212

1313
export interface StartOptions {
1414
root?: string
@@ -88,5 +88,5 @@ export async function build(options: BuildOptions) {
8888
outDir,
8989
})
9090

91-
logger.DTK0010().log()
91+
diagnostics.DTK0010.report()
9292
}

packages/core/src/node/context.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { ResolvedConfig, ViteDevServer } from 'vite'
44
import { createKitContext, createViteDevToolsHost } from '@vitejs/devtools-kit/node'
55
import { isObject } from 'devframe/node'
66
import { createDebug } from 'obug'
7-
import { diagnostics, logger } from './diagnostics'
7+
import { diagnostics } from './diagnostics'
88
import { builtinRpcDeclarations } from './rpc'
99

1010
const debugSetup = createDebug('vite:devtools:context:setup')
@@ -78,7 +78,7 @@ export async function createDevToolsContext(
7878
await plugin.devtools?.setup?.(context)
7979
}
8080
catch (error) {
81-
throw logger.DTK0014({ name: plugin.name }, { cause: error }).throw()
81+
throw diagnostics.DTK0014.throw({ name: plugin.name, cause: error })
8282
}
8383
}
8484

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,44 @@
1-
import { colors as c } from 'devframe/utils/colors'
2-
import { consoleReporter, createLogger, defineDiagnostics } from 'logs-sdk'
3-
import { ansiFormatter } from 'logs-sdk/formatters/ansi'
1+
import { defineDiagnostics, reporterLog } from 'nostics'
42

53
export const diagnostics = defineDiagnostics({
64
docsBase: 'https://devtools.vite.dev/errors',
5+
reporters: [reporterLog],
76
codes: {
87
DTK0008: {
9-
message: 'Client authentication is disabled. Any browser can connect to the devtools and access your server and filesystem.',
10-
level: 'warn',
8+
why: 'Client authentication is disabled. Any browser can connect to the devtools and access your server and filesystem.',
119
},
1210
DTK0010: {
13-
message: 'Static build is still experimental and not yet complete. Generated output may be missing features and can change without notice.',
14-
level: 'warn',
11+
why: 'Static build is still experimental and not yet complete. Generated output may be missing features and can change without notice.',
1512
},
1613
DTK0011: {
17-
message: (p: { name: string }) => `RPC error on executing "${p.name}"`,
14+
why: (p: { name: string }) => `RPC error on executing "${p.name}"`,
1815
},
1916
DTK0012: {
20-
message: 'RPC error on executing rpc',
17+
why: 'RPC error on executing rpc',
2118
},
2219
DTK0013: {
23-
message: (p: { name: string, clientId: string }) => `Unauthorized access to method ${JSON.stringify(p.name)} from client [${p.clientId}]`,
20+
why: (p: { name: string, clientId: string }) => `Unauthorized access to method ${JSON.stringify(p.name)} from client [${p.clientId}]`,
2421
},
2522
DTK0014: {
26-
message: (p: { name: string }) => `Error setting up plugin ${p.name}`,
23+
why: (p: { name: string }) => `Error setting up plugin ${p.name}`,
2724
},
2825
DTK0023: {
29-
message: 'viteServer is required in dev mode',
26+
why: 'viteServer is required in dev mode',
3027
},
3128
DTK0028: {
32-
message: 'Path is outside the workspace root',
29+
why: 'Path is outside the workspace root',
3330
},
3431
DTK0029: {
35-
message: 'Path is outside the workspace root',
32+
why: 'Path is outside the workspace root',
3633
},
3734
DTK0030: {
38-
message: (p: { id: string }) => `Dock entry with id "${p.id}" not found`,
35+
why: (p: { id: string }) => `Dock entry with id "${p.id}" not found`,
3936
},
4037
DTK0031: {
41-
message: (p: { id: string }) => `Dock entry with id "${p.id}" is not a launcher`,
38+
why: (p: { id: string }) => `Dock entry with id "${p.id}" is not a launcher`,
4239
},
4340
DTK0032: {
44-
message: (p: { id: string }) => `Error launching dock entry "${p.id}"`,
41+
why: (p: { id: string }) => `Error launching dock entry "${p.id}"`,
4542
},
4643
},
4744
})
48-
49-
export const logger = createLogger({
50-
diagnostics: [diagnostics],
51-
formatter: ansiFormatter(c),
52-
reporters: consoleReporter,
53-
})

packages/core/src/node/rpc/internal/docks-on-launch.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { defineRpcFunction } from '@vitejs/devtools-kit'
2-
import { logger } from '../../diagnostics'
2+
import { diagnostics } from '../../diagnostics'
33

44
export const docksOnLaunch = defineRpcFunction({
55
name: 'devtoolskit:internal:docks:on-launch',
@@ -14,10 +14,10 @@ export const docksOnLaunch = defineRpcFunction({
1414

1515
const entry = context.docks.values().find(entry => entry.id === entryId)
1616
if (!entry) {
17-
throw logger.DTK0030({ id: entryId }).throw()
17+
throw diagnostics.DTK0030.throw({ id: entryId })
1818
}
1919
if (entry.type !== 'launcher') {
20-
throw logger.DTK0031({ id: entryId }).throw()
20+
throw diagnostics.DTK0031.throw({ id: entryId })
2121
}
2222
try {
2323
context.docks.update({
@@ -43,7 +43,7 @@ export const docksOnLaunch = defineRpcFunction({
4343
return result
4444
}
4545
catch (error) {
46-
logger.DTK0032({ id: entryId }, { cause: error }).log()
46+
diagnostics.DTK0032.report({ id: entryId, cause: error })
4747
context.docks.update({
4848
...entry,
4949
launcher: {

0 commit comments

Comments
 (0)