@djangocfg/devtools 2.1.548 → 2.1.549
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/src/capture.ts +9 -0
- package/src/instance.ts +6 -3
- package/src/internal.ts +13 -0
- package/src/server.ts +5 -2
- package/src/store.ts +10 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/devtools",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.549",
|
|
4
4
|
"description": "Frontend devtools for django-cfg apps: error/console capture with backend ingest, plus a floating debug panel. One package, one event stream.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"django",
|
|
@@ -49,16 +49,16 @@
|
|
|
49
49
|
"lint": "eslint ."
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@djangocfg/ui-core": "^2.1.
|
|
52
|
+
"@djangocfg/ui-core": "^2.1.549",
|
|
53
53
|
"lucide-react": ">=0.400.0",
|
|
54
54
|
"react": "^19.0.0",
|
|
55
55
|
"react-dom": "^19.0.0",
|
|
56
56
|
"zustand": "^5.0.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@djangocfg/eslint-config": "^2.1.
|
|
60
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
61
|
-
"@djangocfg/ui-core": "^2.1.
|
|
59
|
+
"@djangocfg/eslint-config": "^2.1.549",
|
|
60
|
+
"@djangocfg/typescript-config": "^2.1.549",
|
|
61
|
+
"@djangocfg/ui-core": "^2.1.549",
|
|
62
62
|
"@storybook/react-vite": "^10.5.0",
|
|
63
63
|
"@types/node": "^25.9.5",
|
|
64
64
|
"@types/react": "19.2.15",
|
package/src/capture.ts
CHANGED
|
@@ -23,8 +23,17 @@ const HYDRATION_NOISE: RegExp[] = [
|
|
|
23
23
|
/text content does not match server-rendered html/i,
|
|
24
24
|
]
|
|
25
25
|
|
|
26
|
+
// Third-party libraries that log a handled failure through console.error.
|
|
27
|
+
// The call already succeeded or fell back, so reporting it alerts on nothing.
|
|
28
|
+
const LIBRARY_NOISE: RegExp[] = [
|
|
29
|
+
// input-otp probes `:autofill`, which older engines reject, then installs
|
|
30
|
+
// the `-webkit-autofill` rule that actually applies. Nothing is broken.
|
|
31
|
+
/input-otp could not insert CSS rule/i,
|
|
32
|
+
]
|
|
33
|
+
|
|
26
34
|
function isNoise(msg: string, stack?: string): boolean {
|
|
27
35
|
if (HYDRATION_NOISE.some((p) => p.test(msg))) return true
|
|
36
|
+
if (LIBRARY_NOISE.some((p) => p.test(msg))) return true
|
|
28
37
|
// Our own transport failing must never loop back into capture.
|
|
29
38
|
return INGEST_PATTERN.test(msg) || INGEST_PATTERN.test(stack ?? '')
|
|
30
39
|
}
|
package/src/instance.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
installJsErrorCapture,
|
|
14
14
|
installValidationCapture,
|
|
15
15
|
} from './capture'
|
|
16
|
-
import { DEFAULT_FLUSH_INTERVAL, getSessionId, isDevelopment } from './internal'
|
|
16
|
+
import { DEFAULT_FLUSH_INTERVAL, getSessionId, isDevelopment, rawConsole } from './internal'
|
|
17
17
|
import { devtoolsStore } from './store'
|
|
18
18
|
import { EventLevel, EventType } from './types'
|
|
19
19
|
import type { DevtoolsConfig, DevtoolsEvent, Logger } from './types'
|
|
@@ -145,9 +145,12 @@ export const devtools = {
|
|
|
145
145
|
devtoolsStore.getState().addEntry({ level, source, message, data: cleanData, stack })
|
|
146
146
|
}
|
|
147
147
|
if (isDevelopment) {
|
|
148
|
+
// rawConsole, not console: `installConsoleCapture` wraps console.error
|
|
149
|
+
// and console.warn, so printing through them would feed this
|
|
150
|
+
// panel-only line back into capture and ingest it.
|
|
148
151
|
const method = level === 'error' ? 'error' : level === 'warn' ? 'warn' : 'log'
|
|
149
|
-
if (cleanData)
|
|
150
|
-
else
|
|
152
|
+
if (cleanData) rawConsole[method](`[${source}]`, message, cleanData)
|
|
153
|
+
else rawConsole[method](`[${source}]`, message)
|
|
151
154
|
}
|
|
152
155
|
}
|
|
153
156
|
return {
|
package/src/internal.ts
CHANGED
|
@@ -83,3 +83,16 @@ export function makeDeduper(max = 200): (key: string, ttl: number) => boolean {
|
|
|
83
83
|
export function truncate(s: string, max: number): string {
|
|
84
84
|
return s.length > max ? s.slice(0, max - 1) + '…' : s
|
|
85
85
|
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The console as it was before `installConsoleCapture` wrapped it.
|
|
89
|
+
*
|
|
90
|
+
* Bound at module load, which runs before any install, so callers that must
|
|
91
|
+
* NOT feed capture — the local logger — can still print. Going through the
|
|
92
|
+
* wrapped console instead turns a panel-only log line into an ingested event.
|
|
93
|
+
*/
|
|
94
|
+
export const rawConsole = {
|
|
95
|
+
log: console.log.bind(console),
|
|
96
|
+
warn: console.warn.bind(console),
|
|
97
|
+
error: console.error.bind(console),
|
|
98
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -27,10 +27,13 @@ let _config: ServerDevtoolsConfig = {}
|
|
|
27
27
|
|
|
28
28
|
async function send(events: DevtoolsEvent[]): Promise<void> {
|
|
29
29
|
try {
|
|
30
|
+
// `??`, not a spread of the config underneath: an event carrying an
|
|
31
|
+
// explicit `project_name: undefined` would otherwise blank the configured
|
|
32
|
+
// one. Same precedence as the browser store.
|
|
30
33
|
await sendBatch(_config.baseUrl ?? '', events.map((e) => ({
|
|
31
|
-
project_name: _config.project,
|
|
32
|
-
environment: _config.environment,
|
|
33
34
|
...e,
|
|
35
|
+
project_name: e.project_name ?? _config.project,
|
|
36
|
+
environment: e.environment ?? _config.environment,
|
|
34
37
|
})))
|
|
35
38
|
} catch {
|
|
36
39
|
// Never throw from error reporting
|
package/src/store.ts
CHANGED
|
@@ -24,6 +24,9 @@ import type { DevtoolsConfig, DevtoolsEvent, LogEntry, LogLevel } from './types'
|
|
|
24
24
|
|
|
25
25
|
const MAX_ENTRIES = 1000
|
|
26
26
|
const MAX_BATCH = 25
|
|
27
|
+
// Ceiling for events held while ingest is failing. The oldest are dropped
|
|
28
|
+
// first: during an outage the newest events describe the current state.
|
|
29
|
+
const MAX_OUTBOX = 200
|
|
27
30
|
|
|
28
31
|
// Circuit breaker: pause ingest after N consecutive transport failures
|
|
29
32
|
const BREAKER_THRESHOLD = 3
|
|
@@ -152,7 +155,14 @@ export const devtoolsStore = createStore<DevtoolsState>((set, get) => ({
|
|
|
152
155
|
() => set({ _failures: 0, _pausedUntil: 0 }),
|
|
153
156
|
() => {
|
|
154
157
|
const failures = get()._failures + 1
|
|
158
|
+
// Put the batch back, ahead of whatever arrived meanwhile: a failed
|
|
159
|
+
// send must not consume the events. Dropping them here would lose
|
|
160
|
+
// exactly the reports of an incident the backend was too busy to take.
|
|
161
|
+
// A beacon flush is the exception — the page is going away and there
|
|
162
|
+
// is no later attempt to hand them to.
|
|
163
|
+
const requeued = useBeacon ? get().outbox : [...batch, ...get().outbox]
|
|
155
164
|
set({
|
|
165
|
+
outbox: requeued.length > MAX_OUTBOX ? requeued.slice(-MAX_OUTBOX) : requeued,
|
|
156
166
|
_failures: failures,
|
|
157
167
|
_pausedUntil:
|
|
158
168
|
failures >= BREAKER_THRESHOLD ? Date.now() + BREAKER_COOLDOWN_MS : get()._pausedUntil,
|