@expofp/debug 3.1.8 → 3.1.10

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 CHANGED
@@ -1,3 +1,3 @@
1
- export { initDebug } from './lib/init-debug.js';
1
+ import './lib/init-debug.js';
2
2
  export * from './lib/settings/index.js';
3
3
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { initDebug } from './lib/init-debug.js';
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
+ }
@@ -1,2 +1,2 @@
1
- export declare function initDebug(): void;
1
+ export {};
2
2
  //# sourceMappingURL=init-debug.d.ts.map
@@ -1,14 +1,21 @@
1
- let debugInitialized = false;
2
- export function initDebug() {
3
- if (debugInitialized) {
4
- return;
5
- }
6
- debugInitialized = true;
7
- // if not browser, exit
8
- if (typeof window === 'undefined' && typeof document === 'undefined') {
9
- return;
10
- }
11
- // add a simple debug button to the page (right, center, fixed position)
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
+ }
@@ -1,4 +1,5 @@
1
1
  export * from './register-boolean-setting.js';
2
+ export * from './register-debug-namespaces-setting.js';
2
3
  export * from './register-enum-setting.js';
3
4
  export * from './register-setting.js';
4
5
  export * from './reset-all-settings.js';
@@ -1,4 +1,5 @@
1
1
  export * from './register-boolean-setting.js';
2
+ export * from './register-debug-namespaces-setting.js';
2
3
  export * from './register-enum-setting.js';
3
4
  export * from './register-setting.js';
4
5
  export * from './reset-all-settings.js';
@@ -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,2 @@
1
+ export declare const DebugNamespacesEditor: React.FC;
2
+ //# sourceMappingURL=debug-namespaces-editor.d.ts.map
@@ -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';
@@ -1,6 +1,46 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
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
- return (_jsx(Box, { pt: "20px", children: _jsx(Box, { mb: "20px", children: _jsxs(Callout.Root, { children: [_jsx(Callout.Icon, { children: _jsx(InfoCircledIcon, {}) }), _jsx(Callout.Text, { children: "Implement to intercept console.info, console.warn and console.error and output them here when logging enabled." })] }) }) }));
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');
@@ -0,0 +1,3 @@
1
+ import { type LogEntry } from './log-store.js';
2
+ export declare function useLogBuffer(): readonly LogEntry[];
3
+ //# sourceMappingURL=use-log-buffer.d.ts.map
@@ -0,0 +1,5 @@
1
+ import { useSyncExternalStore } from 'react';
2
+ import { getLogSnapshot, subscribeLogStore } from './log-store.js';
3
+ export function useLogBuffer() {
4
+ return useSyncExternalStore(subscribeLogStore, getLogSnapshot, getLogSnapshot);
5
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expofp/debug",
3
- "version": "3.1.8",
3
+ "version": "3.1.10",
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": {