Skip to content

Commit d88cf5c

Browse files
committed
refactor(devframe): centralize diagnostics reporter in devframe/utils/nostics
Move the ANSI console reporter registration into the shared `devframe/utils/nostics` `defineDiagnostics` wrapper, which pre-wires it ahead of any caller-supplied reporters. Every module-level `diagnostics.ts` (devframe core, @devframes/hub, @devframes/json-render, and the built-in plugins) now just calls `defineDiagnostics({ docsBase, codes })` — no local reporter, no `colors`/`ansiFormatter` imports. Delete the per-package `diagnostics-reporter.ts` files and collapse the host's `defineDiagnostics` to the shared wrapper (it already prepends the reporter, so no extra merging is needed).
1 parent 7b8b3b2 commit d88cf5c

20 files changed

Lines changed: 84 additions & 140 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ Any change to one lands in the other in the same PR: adding a dock, wiring a new
9191

9292
## Structured Diagnostics (Error Codes)
9393

94-
All node-side warnings and errors use structured diagnostics via [`nostics`](https://www.npmjs.com/package/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. Import `defineDiagnostics`, `Diagnostic`, and `ansiFormatter` from `devframe/utils/nostics` rather than from `nostics` directly - it re-exports the package's API so plugins don't need their own `nostics` dependency.
94+
All node-side warnings and errors use structured diagnostics via [`nostics`](https://www.npmjs.com/package/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. Import `defineDiagnostics` (and `Diagnostic` for `instanceof` checks) from `devframe/utils/nostics` rather than from `nostics` directly - it pre-wires devframe's ANSI console reporter, so a plugin's `diagnostics.ts` never builds its own reporter (`colors`, `ansiFormatter`) or depends on `nostics` itself.
9595

9696
Prefix: **`DF`**. Codes are sequential 4-digit numbers (e.g. `DF0033`). Check the existing diagnostics file to find the next available number.
9797

packages/devframe/src/node/diagnostics.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import { defineDiagnostics } from 'devframe/utils/nostics'
2-
import { devframeReporter } from '../utils/diagnostics-reporter'
32

43
// DF00xx codes are allocated across packages (e.g. @devframes/json-render
54
// owns DF0037–DF0041), so this file alone doesn't show the next free
65
// number — check `docs/errors/` for the full allocation before adding one.
76
export const diagnostics = defineDiagnostics({
87
docsBase: 'https://devfra.me/errors',
9-
reporters: [devframeReporter],
108
codes: {
119
DF0006: {
1210
why: (p: { name: string }) => `RPC function "${p.name}" is not registered`,

packages/devframe/src/node/host-diagnostics.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { DevframeDiagnosticsHost as DevframeDiagnosticsHostType, DevframeDiagnosticsLogger, DevframeNodeContext } from 'devframe/types'
22
import { defineDiagnostics } from 'devframe/utils/nostics'
3-
import { devframeReporter } from '../utils/diagnostics-reporter'
43

54
export class DevframeDiagnosticsHost implements DevframeDiagnosticsHostType {
65
private _registry: Record<string, unknown> = {}
@@ -9,16 +8,9 @@ export class DevframeDiagnosticsHost implements DevframeDiagnosticsHostType {
98
get: (_, code: string) => this._registry[code],
109
})
1110

12-
readonly defineDiagnostics: DevframeDiagnosticsHostType['defineDiagnostics'] = (opts) => {
13-
const merged = {
14-
...opts,
15-
reporters: [devframeReporter, ...(opts.reporters ?? [])],
16-
} as Parameters<typeof defineDiagnostics>[0]
17-
// Runtime passthrough: the per-call `Codes` generic can't be threaded
18-
// through this assigned arrow, so the narrow return type is restored by
19-
// the property's declared signature at every call site.
20-
return defineDiagnostics(merged) as any
21-
}
11+
// Already pre-wires devframe's ANSI console reporter — no extra merging
12+
// needed here, the host's `defineDiagnostics` just is the shared one.
13+
readonly defineDiagnostics: DevframeDiagnosticsHostType['defineDiagnostics'] = defineDiagnostics
2214

2315
constructor(
2416
public readonly context: DevframeNodeContext,

packages/devframe/src/rpc/diagnostics.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import { defineDiagnostics } from 'devframe/utils/nostics'
2-
import { devframeReporter } from '../utils/diagnostics-reporter'
32

43
export const diagnostics = defineDiagnostics({
54
docsBase: 'https://devfra.me/errors',
6-
reporters: [devframeReporter],
75
codes: {
86
DF0019: {
97
why: (p: { name: string }) =>

packages/devframe/src/types/diagnostics.ts

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { defineDiagnostics, Diagnostic, DiagnosticDefinition } from 'devframe/utils/nostics'
1+
import type { defineDiagnostics } from 'devframe/utils/nostics'
22

33
/**
44
* The shared diagnostics lookup exposed by the host. A `Proxy` that resolves
@@ -10,15 +10,14 @@ import type { defineDiagnostics, Diagnostic, DiagnosticDefinition } from 'devfra
1010
export type DevframeDiagnosticsLogger = Record<string, any>
1111

1212
/**
13-
* Options accepted by the host's `defineDiagnostics()` factory — mirrors
14-
* `nostics`'s shape but the host pre-wires its ANSI console reporter, so
15-
* plugins typically omit `reporters`.
13+
* Options accepted by the host's `defineDiagnostics()` factory. Re-exported
14+
* from `devframe/utils/nostics` — the same shape every module-level
15+
* `diagnostics.ts` (devframe core, `@devframes/hub`, the built-in plugins)
16+
* accepts, since `host.defineDiagnostics()` and the top-level
17+
* `defineDiagnostics` from `devframe/utils/nostics` pre-wire the identical
18+
* ANSI console reporter.
1619
*/
17-
export interface DevframeDefineDiagnosticsOptions<Codes extends Record<string, DiagnosticDefinition>> {
18-
docsBase?: string | ((code: keyof Codes) => string | undefined)
19-
codes: Codes
20-
reporters?: ReadonlyArray<(d: Diagnostic, o?: any) => void>
21-
}
20+
export type { DevframeDefineDiagnosticsOptions } from 'devframe/utils/nostics'
2221

2322
/**
2423
* Host for structured diagnostics — a thin layer over `nostics` that lets
@@ -64,10 +63,9 @@ export interface DevframeDiagnosticsHost {
6463

6564
/**
6665
* Build a typed diagnostics object with the host's ANSI console reporter
67-
* pre-wired. Mirrors `nostics`'s `defineDiagnostics` so integrations don't
68-
* need to take a direct dependency on `nostics`.
66+
* pre-wired. The same `devframe/utils/nostics` `defineDiagnostics` every
67+
* built-in plugin's module-level `diagnostics.ts` uses, so integrations
68+
* don't need to take a direct dependency on `nostics`.
6969
*/
70-
defineDiagnostics: <const Codes extends Record<string, DiagnosticDefinition>>(
71-
options: DevframeDefineDiagnosticsOptions<Codes>,
72-
) => ReturnType<typeof defineDiagnostics<Codes, any>>
70+
defineDiagnostics: typeof defineDiagnostics
7371
}

packages/devframe/src/utils/diagnostics-reporter.ts

Lines changed: 0 additions & 12 deletions
This file was deleted.

packages/devframe/src/utils/nostics.ts

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,55 @@
1+
import type { AnyDiagnosticReporter, Diagnostic, DiagnosticDefinition, Diagnostics } from 'nostics'
2+
import { colors } from 'devframe/utils/colors'
3+
import { defineDiagnostics as defineNosticsDiagnostics } from 'nostics'
4+
import { ansiFormatter } from 'nostics/formatters/ansi'
5+
6+
const formatAnsi = ansiFormatter(colors)
7+
8+
/**
9+
* The reporter every {@link defineDiagnostics} call below wires in ahead of
10+
* any caller-supplied ones: prints the diagnostic through devframe's own
11+
* ANSI colors via `console[method]` (default `'warn'`).
12+
*/
13+
function devframeReporter(d: Diagnostic, { method = 'warn' }: { method?: 'log' | 'warn' | 'error' } = {}): void {
14+
// eslint-disable-next-line no-console
15+
console[method](formatAnsi(d))
16+
}
17+
118
/**
2-
* Re-exports `nostics`'s public API — `defineDiagnostics`, the `Diagnostic`
3-
* class, its supporting types, and the ANSI formatter — so integrations that
4-
* define their own coded `diagnostics.ts` (the built-in plugins,
5-
* `@devframes/hub`, `@devframes/json-render`, …) reach it through
6-
* `devframe/utils/nostics` instead of taking a direct dependency on
7-
* `nostics` themselves.
19+
* Options accepted by {@link defineDiagnostics} — identical to `nostics`'s
20+
* own `DefineDiagnosticsOptions`, minus the reporter devframe already
21+
* prepends.
822
*/
23+
export type DevframeDefineDiagnosticsOptions<
24+
Codes extends Record<string, DiagnosticDefinition>,
25+
Reporters extends readonly AnyDiagnosticReporter[] = [],
26+
> = Parameters<typeof defineDiagnostics<Codes, Reporters>>[0]
27+
28+
/**
29+
* Drop-in replacement for `nostics`'s `defineDiagnostics()` with devframe's
30+
* ANSI console reporter pre-wired ahead of any `reporters` passed in. Every
31+
* `diagnostics.ts` in devframe core, `@devframes/hub`, `@devframes/json-render`,
32+
* and the built-in plugins defines its codes through this instead of
33+
* `nostics`'s own `defineDiagnostics` — the reporter registration lives
34+
* here, once, so none of them need to build their own reporter (`colors`,
35+
* `ansiFormatter`) or take a direct dependency on `nostics` themselves.
36+
*/
37+
export function defineDiagnostics<
38+
const Codes extends Record<string, DiagnosticDefinition>,
39+
const Reporters extends readonly AnyDiagnosticReporter[] = [],
40+
>(options: {
41+
docsBase?: string | ((code: keyof Codes) => string | undefined)
42+
codes: Codes
43+
reporters?: Reporters
44+
}): Diagnostics<Codes, readonly [typeof devframeReporter, ...Reporters]> {
45+
return defineNosticsDiagnostics({
46+
...options,
47+
reporters: [devframeReporter, ...(options.reporters ?? [])],
48+
}) as Diagnostics<Codes, readonly [typeof devframeReporter, ...Reporters]>
49+
}
50+
951
export {
1052
createConsoleReporter,
11-
defineDiagnostics,
1253
defineProdDiagnostics,
1354
Diagnostic,
1455
formatDiagnostic,
@@ -18,7 +59,6 @@ export type {
1859
AnyDiagnosticReporter,
1960
ConsoleMethod,
2061
ConsoleReporterOptions,
21-
DefineDiagnosticsOptions,
2262
DiagnosticCallParams,
2363
DiagnosticDefinition,
2464
DiagnosticHandle,

packages/hub/src/node/diagnostics.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { defineDiagnostics } from 'devframe/utils/nostics'
2-
import { hubReporter } from '../utils/diagnostics-reporter'
32

43
// Hub-side diagnostics for docks, terminals, messages, and commands.
54
// Shares the `DF` prefix with devframe core; the hub reserves the
@@ -12,7 +11,6 @@ import { hubReporter } from '../utils/diagnostics-reporter'
1211
// DF8400-DF8499 — commands
1312
export const diagnostics = defineDiagnostics({
1413
docsBase: 'https://devfra.me/errors',
15-
reporters: [hubReporter],
1614
codes: {
1715
DF8000: {
1816
why: (p: { id: string }) => `Devframe id "${p.id}" collides with a reserved hub path — it cannot be mounted directly under the hub base.`,

packages/hub/src/utils/diagnostics-reporter.ts

Lines changed: 0 additions & 12 deletions
This file was deleted.

packages/json-render/src/node/diagnostics.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,11 @@
1-
import type { Diagnostic } from 'devframe/utils/nostics'
2-
import { colors as c } from 'devframe/utils/colors'
3-
import { ansiFormatter, defineDiagnostics } from 'devframe/utils/nostics'
4-
5-
const formatAnsi = ansiFormatter(c)
6-
7-
interface ReporterOptions { method?: 'log' | 'warn' | 'error' }
8-
9-
function jsonRenderReporter(d: Diagnostic, { method = 'warn' }: ReporterOptions = {}): void {
10-
// eslint-disable-next-line no-console
11-
console[method](formatAnsi(d))
12-
}
1+
import { defineDiagnostics } from 'devframe/utils/nostics'
132

143
// `@devframes/json-render` protocol/runtime diagnostics. These share the
154
// `DF` prefix and live in the devframe core range (next free after the
165
// current highest `DF00xx`, DF0037). Browser-only render failures keep
176
// `console.*` in the UI package.
187
export const diagnostics = defineDiagnostics({
198
docsBase: 'https://devfra.me/errors',
20-
reporters: [jsonRenderReporter],
219
codes: {
2210
DF0038: {
2311
why: (p: { id: string, key: string, issues: string }) =>

0 commit comments

Comments
 (0)