@expofp/debug 3.1.8 → 3.1.9
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/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/format-console-args.d.ts +16 -0
- package/dist/lib/format-console-args.js +95 -0
- package/dist/lib/init-debug.d.ts +1 -1
- package/dist/lib/init-debug.js +18 -11
- package/dist/lib/log-store.d.ts +11 -0
- package/dist/lib/log-store.js +22 -0
- package/dist/lib/settings/index.d.ts +1 -0
- package/dist/lib/settings/index.js +1 -0
- package/dist/lib/settings/register-debug-namespaces-setting.d.ts +6 -0
- package/dist/lib/settings/register-debug-namespaces-setting.js +26 -0
- package/dist/lib/ui/debug-namespaces-editor.d.ts +2 -0
- package/dist/lib/ui/debug-namespaces-editor.js +32 -0
- package/dist/lib/ui/log-tab.js +43 -3
- package/dist/lib/ui/render-debug-ui.js +2 -0
- package/dist/lib/use-log-buffer.d.ts +3 -0
- package/dist/lib/use-log-buffer.js +5 -0
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import './lib/init-debug.js';
|
|
2
2
|
export * from './lib/settings/index.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
export type FormattedSegment = {
|
|
3
|
+
text: string;
|
|
4
|
+
style: CSSProperties;
|
|
5
|
+
};
|
|
6
|
+
export type FormattedArgs = {
|
|
7
|
+
segments: FormattedSegment[];
|
|
8
|
+
trailing: unknown[];
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Parse a console-style format string with `%c`/`%s`/`%d`/`%i`/`%f`/`%o`/`%O`/`%%`
|
|
12
|
+
* into styled text segments plus any trailing unconsumed args. Mirrors browser
|
|
13
|
+
* DevTools formatting so colored `console.log` output can be rendered in the UI.
|
|
14
|
+
*/
|
|
15
|
+
export declare function formatConsoleArgs(args: unknown[]): FormattedArgs;
|
|
16
|
+
//# sourceMappingURL=format-console-args.d.ts.map
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a console-style format string with `%c`/`%s`/`%d`/`%i`/`%f`/`%o`/`%O`/`%%`
|
|
3
|
+
* into styled text segments plus any trailing unconsumed args. Mirrors browser
|
|
4
|
+
* DevTools formatting so colored `console.log` output can be rendered in the UI.
|
|
5
|
+
*/
|
|
6
|
+
export function formatConsoleArgs(args) {
|
|
7
|
+
const fmt = args[0];
|
|
8
|
+
if (typeof fmt !== 'string' || !fmt.includes('%')) {
|
|
9
|
+
return { segments: [], trailing: args };
|
|
10
|
+
}
|
|
11
|
+
const segments = [];
|
|
12
|
+
let style = {};
|
|
13
|
+
let buf = '';
|
|
14
|
+
let argIdx = 1;
|
|
15
|
+
let i = 0;
|
|
16
|
+
const flush = () => {
|
|
17
|
+
if (buf.length > 0) {
|
|
18
|
+
segments.push({ text: buf, style });
|
|
19
|
+
buf = '';
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
while (i < fmt.length) {
|
|
23
|
+
const ch = fmt[i];
|
|
24
|
+
if (ch !== '%' || i === fmt.length - 1) {
|
|
25
|
+
buf += ch;
|
|
26
|
+
i++;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const directive = fmt[i + 1];
|
|
30
|
+
switch (directive) {
|
|
31
|
+
case 'c': {
|
|
32
|
+
flush();
|
|
33
|
+
const css = args[argIdx++];
|
|
34
|
+
style = typeof css === 'string' ? parseCss(css) : {};
|
|
35
|
+
i += 2;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
case 's': {
|
|
39
|
+
buf += String(args[argIdx++] ?? '');
|
|
40
|
+
i += 2;
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
case 'd':
|
|
44
|
+
case 'i': {
|
|
45
|
+
const n = Number(args[argIdx++]);
|
|
46
|
+
buf += Number.isNaN(n) ? 'NaN' : String(Math.trunc(n));
|
|
47
|
+
i += 2;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
case 'f': {
|
|
51
|
+
buf += String(Number(args[argIdx++]));
|
|
52
|
+
i += 2;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
case 'o':
|
|
56
|
+
case 'O': {
|
|
57
|
+
const v = args[argIdx++];
|
|
58
|
+
try {
|
|
59
|
+
buf += JSON.stringify(v);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
buf += String(v);
|
|
63
|
+
}
|
|
64
|
+
i += 2;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
case '%': {
|
|
68
|
+
buf += '%';
|
|
69
|
+
i += 2;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
default: {
|
|
73
|
+
buf += ch;
|
|
74
|
+
i++;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
flush();
|
|
79
|
+
return { segments, trailing: args.slice(argIdx) };
|
|
80
|
+
}
|
|
81
|
+
function parseCss(css) {
|
|
82
|
+
const out = {};
|
|
83
|
+
for (const decl of css.split(';')) {
|
|
84
|
+
const colonIdx = decl.indexOf(':');
|
|
85
|
+
if (colonIdx === -1)
|
|
86
|
+
continue;
|
|
87
|
+
const prop = decl.slice(0, colonIdx).trim();
|
|
88
|
+
const val = decl.slice(colonIdx + 1).trim();
|
|
89
|
+
if (!prop || !val)
|
|
90
|
+
continue;
|
|
91
|
+
const camel = prop.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
92
|
+
out[camel] = val;
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
package/dist/lib/init-debug.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export
|
|
1
|
+
export {};
|
|
2
2
|
//# sourceMappingURL=init-debug.d.ts.map
|
package/dist/lib/init-debug.js
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import { appendLog } from './log-store.js';
|
|
2
|
+
if (typeof console !== 'undefined') {
|
|
3
|
+
const wrap = (level, original) => (...args) => {
|
|
4
|
+
appendLog({ level, args, ts: Date.now() });
|
|
5
|
+
original(...args);
|
|
6
|
+
};
|
|
7
|
+
const originalLog = console.log.bind(console);
|
|
8
|
+
const originalDebug = console.debug.bind(console);
|
|
9
|
+
const originalInfo = console.info.bind(console);
|
|
10
|
+
const originalWarn = console.warn.bind(console);
|
|
11
|
+
const originalError = console.error.bind(console);
|
|
12
|
+
console.log = wrap('log', originalLog);
|
|
13
|
+
console.debug = wrap('debug', originalDebug);
|
|
14
|
+
console.info = wrap('info', originalInfo);
|
|
15
|
+
console.warn = wrap('warn', originalWarn);
|
|
16
|
+
console.error = wrap('error', originalError);
|
|
17
|
+
}
|
|
18
|
+
if (typeof window !== 'undefined' || typeof document !== 'undefined') {
|
|
12
19
|
import('./ui/index.js')
|
|
13
20
|
.then(({ renderDebugUi }) => {
|
|
14
21
|
renderDebugUi();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type LogLevel = 'log' | 'debug' | 'info' | 'warn' | 'error';
|
|
2
|
+
export type LogEntry = {
|
|
3
|
+
level: LogLevel;
|
|
4
|
+
args: unknown[];
|
|
5
|
+
ts: number;
|
|
6
|
+
};
|
|
7
|
+
export declare function getLogSnapshot(): readonly LogEntry[];
|
|
8
|
+
export declare function subscribeLogStore(listener: () => void): () => void;
|
|
9
|
+
export declare function appendLog(entry: LogEntry): void;
|
|
10
|
+
export declare function clearLogs(): void;
|
|
11
|
+
//# sourceMappingURL=log-store.d.ts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const MAX = 5000;
|
|
2
|
+
let snapshot = [];
|
|
3
|
+
const listeners = new Set();
|
|
4
|
+
export function getLogSnapshot() {
|
|
5
|
+
return snapshot;
|
|
6
|
+
}
|
|
7
|
+
export function subscribeLogStore(listener) {
|
|
8
|
+
listeners.add(listener);
|
|
9
|
+
return () => {
|
|
10
|
+
listeners.delete(listener);
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function appendLog(entry) {
|
|
14
|
+
snapshot = snapshot.length >= MAX ? [...snapshot.slice(1), entry] : [...snapshot, entry];
|
|
15
|
+
listeners.forEach((l) => l());
|
|
16
|
+
}
|
|
17
|
+
export function clearLogs() {
|
|
18
|
+
if (snapshot.length === 0)
|
|
19
|
+
return;
|
|
20
|
+
snapshot = [];
|
|
21
|
+
listeners.forEach((l) => l());
|
|
22
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const STORAGE_KEY = "debug";
|
|
2
|
+
export declare const LOCAL_STORAGE_EVENT = "efp-local-storage";
|
|
3
|
+
export declare function registerDebugNamespacesSetting(): void;
|
|
4
|
+
export declare function getDebugNamespaces(): string;
|
|
5
|
+
export declare function setDebugNamespaces(pattern: string): void;
|
|
6
|
+
//# sourceMappingURL=register-debug-namespaces-setting.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import debug from 'debug';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { editors, resets } from './setting-registry.js';
|
|
4
|
+
export const STORAGE_KEY = 'debug';
|
|
5
|
+
export const LOCAL_STORAGE_EVENT = 'efp-local-storage';
|
|
6
|
+
export function registerDebugNamespacesSetting() {
|
|
7
|
+
const DebugNamespacesEditorLazy = React.lazy(() => import('../ui/debug-namespaces-editor.js').then((mod) => ({
|
|
8
|
+
default: mod.DebugNamespacesEditor,
|
|
9
|
+
})));
|
|
10
|
+
resets.push(() => setDebugNamespaces(''));
|
|
11
|
+
editors.push(DebugNamespacesEditorLazy);
|
|
12
|
+
}
|
|
13
|
+
export function getDebugNamespaces() {
|
|
14
|
+
return localStorage.getItem(STORAGE_KEY) ?? '';
|
|
15
|
+
}
|
|
16
|
+
export function setDebugNamespaces(pattern) {
|
|
17
|
+
if (pattern) {
|
|
18
|
+
localStorage.setItem(STORAGE_KEY, pattern);
|
|
19
|
+
debug.enable(pattern);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
23
|
+
debug.disable();
|
|
24
|
+
}
|
|
25
|
+
window.dispatchEvent(new CustomEvent(LOCAL_STORAGE_EVENT, { detail: { key: STORAGE_KEY } }));
|
|
26
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { TextField } from '@radix-ui/themes';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { getDebugNamespaces, LOCAL_STORAGE_EVENT, setDebugNamespaces, STORAGE_KEY, } from '../settings/register-debug-namespaces-setting.js';
|
|
5
|
+
import { SettingsItem } from './settings-item.js';
|
|
6
|
+
export const DebugNamespacesEditor = () => {
|
|
7
|
+
const [value, setValue] = useState(getDebugNamespaces);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const update = () => setValue(getDebugNamespaces());
|
|
10
|
+
const onLocal = (e) => {
|
|
11
|
+
const detail = e.detail;
|
|
12
|
+
if (detail?.key === STORAGE_KEY)
|
|
13
|
+
update();
|
|
14
|
+
};
|
|
15
|
+
const onStorage = (e) => {
|
|
16
|
+
if (e.key === STORAGE_KEY)
|
|
17
|
+
update();
|
|
18
|
+
};
|
|
19
|
+
window.addEventListener('storage', onStorage);
|
|
20
|
+
window.addEventListener(LOCAL_STORAGE_EVENT, onLocal);
|
|
21
|
+
return () => {
|
|
22
|
+
window.removeEventListener('storage', onStorage);
|
|
23
|
+
window.removeEventListener(LOCAL_STORAGE_EVENT, onLocal);
|
|
24
|
+
};
|
|
25
|
+
}, []);
|
|
26
|
+
return (_jsx(SettingsItem, { label: "Debug namespaces", children: _jsx(TextField.Root, { placeholder: "e.g. efp:*", value: value, onChange: (e) => {
|
|
27
|
+
const next = e.target.value;
|
|
28
|
+
setValue(next);
|
|
29
|
+
setDebugNamespaces(next);
|
|
30
|
+
}, style: { minWidth: 220 } }) }));
|
|
31
|
+
};
|
|
32
|
+
DebugNamespacesEditor.displayName = 'DebugNamespacesEditor';
|
package/dist/lib/ui/log-tab.js
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { InfoCircledIcon } from '@radix-ui/react-icons';
|
|
3
|
-
import { Box, Callout } from '@radix-ui/themes';
|
|
3
|
+
import { Box, Button, Callout, Code, Flex } from '@radix-ui/themes';
|
|
4
|
+
import { formatConsoleArgs } from '../format-console-args.js';
|
|
5
|
+
import { clearLogs } from '../log-store.js';
|
|
6
|
+
import { useLogBuffer } from '../use-log-buffer.js';
|
|
4
7
|
export const LogTab = () => {
|
|
5
|
-
|
|
8
|
+
const logs = useLogBuffer();
|
|
9
|
+
return (_jsxs(Box, { pt: "20px", children: [_jsxs(Flex, { justify: "between", align: "center", mb: "3", children: [_jsxs(Box, { children: [logs.length, " ", logs.length === 1 ? 'entry' : 'entries'] }), _jsx(Button, { size: "1", variant: "soft", onClick: clearLogs, disabled: logs.length === 0, children: "Clear" })] }), logs.length === 0 ? (_jsxs(Callout.Root, { children: [_jsx(Callout.Icon, { children: _jsx(InfoCircledIcon, {}) }), _jsx(Callout.Text, { children: "No logs yet \u2014 console.log / info / warn / error calls will appear here." })] })) : (logs.map((entry, i) => _jsx(LogRow, { entry: entry }, i)))] }));
|
|
6
10
|
};
|
|
11
|
+
const levelColor = {
|
|
12
|
+
log: undefined,
|
|
13
|
+
debug: 'gray',
|
|
14
|
+
info: 'blue',
|
|
15
|
+
warn: 'amber',
|
|
16
|
+
error: 'red',
|
|
17
|
+
};
|
|
18
|
+
const LogRow = ({ entry }) => {
|
|
19
|
+
const { segments, trailing } = formatConsoleArgs(entry.args);
|
|
20
|
+
return (_jsxs(Box, { mb: "1", style: {
|
|
21
|
+
fontFamily: 'monospace',
|
|
22
|
+
fontSize: 12,
|
|
23
|
+
whiteSpace: 'pre-wrap',
|
|
24
|
+
wordBreak: 'break-word',
|
|
25
|
+
}, children: [_jsx(Code, { size: "1", variant: "ghost", children: formatTime(entry.ts) }), ' ', _jsxs(Code, { size: "1", variant: "ghost", color: levelColor[entry.level], children: ["[", entry.level, "]"] }), ' ', segments.length > 0
|
|
26
|
+
? renderFormatted(segments, trailing)
|
|
27
|
+
: entry.args.map((a, i) => (_jsxs("span", { children: [i > 0 ? ' ' : '', formatArg(a)] }, i)))] }));
|
|
28
|
+
};
|
|
29
|
+
function renderFormatted(segments, trailing) {
|
|
30
|
+
return (_jsxs(_Fragment, { children: [segments.map((s, i) => (_jsx("span", { style: s.style, children: s.text }, i))), trailing.map((a, i) => (_jsxs("span", { children: [" ", formatArg(a)] }, `t-${i}`)))] }));
|
|
31
|
+
}
|
|
32
|
+
function formatTime(ts) {
|
|
33
|
+
return new Date(ts).toISOString().slice(11, 23);
|
|
34
|
+
}
|
|
35
|
+
function formatArg(a) {
|
|
36
|
+
if (typeof a === 'string')
|
|
37
|
+
return a;
|
|
38
|
+
if (a instanceof Error)
|
|
39
|
+
return `${a.name}: ${a.message}`;
|
|
40
|
+
try {
|
|
41
|
+
return JSON.stringify(a);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return String(a);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { registerBooleanSetting } from '../settings/index.js';
|
|
3
|
+
import { registerDebugNamespacesSetting } from '../settings/register-debug-namespaces-setting.js';
|
|
3
4
|
import { addDebugSecretListener } from '../utils/add-debug-secret-listener.js';
|
|
4
5
|
let rendered = false;
|
|
5
6
|
export function renderDebugUi() {
|
|
@@ -8,6 +9,7 @@ export function renderDebugUi() {
|
|
|
8
9
|
}
|
|
9
10
|
rendered = true;
|
|
10
11
|
const showDebugButtonSetting = registerBooleanSetting('Show Debug Button Overlay');
|
|
12
|
+
registerDebugNamespacesSetting();
|
|
11
13
|
const render = async (open) => {
|
|
12
14
|
const { DebugUi } = await import('./debug-ui.js');
|
|
13
15
|
const { createRoot } = await import('react-dom/client');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expofp/debug",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "ExpoFP SDK internal: debug utilities",
|
|
6
6
|
"homepage": "https://developer.expofp.com/",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@radix-ui/react-icons": "^1.3.2",
|
|
30
30
|
"@radix-ui/themes": "^3.2.1",
|
|
31
|
+
"debug": "^4.4.3",
|
|
31
32
|
"tslib": "^2.3.0"
|
|
32
33
|
},
|
|
33
34
|
"peerDependencies": {
|