debug-better 1.0.0

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/src/common.ts ADDED
@@ -0,0 +1,306 @@
1
+ /**
2
+ * Common logic for both Node.js and browser implementations
3
+ */
4
+
5
+ import ms = require('ms');
6
+ import {
7
+ Debugger,
8
+ DebugFactory,
9
+ DebugOptions,
10
+ EnvironmentConfig,
11
+ FilterOptions,
12
+ } from './types';
13
+ import { FilterManager, globalFilter } from './filter';
14
+
15
+ /**
16
+ * Setup function that creates the debug factory with environment-specific config
17
+ */
18
+ export function setup(env: EnvironmentConfig): DebugFactory {
19
+ /**
20
+ * Create a new debugger instance
21
+ */
22
+ function createDebug(namespace: string, options: DebugOptions = {}): Debugger {
23
+ let prevTime: number | undefined;
24
+ let enableOverride: boolean | null = null;
25
+ let namespacesCache: string;
26
+ let enabledCache: boolean;
27
+
28
+ // Instance-specific filter
29
+ const instanceFilter = new FilterManager(options.filter);
30
+
31
+ function debug(...args: any[]): void {
32
+ // Check if disabled (using the property that will be defined below)
33
+ if (!(debug as any).enabled) {
34
+ return;
35
+ }
36
+
37
+ // Apply global and instance filters
38
+ if (!globalFilter.shouldLog(namespace, args)) {
39
+ return;
40
+ }
41
+
42
+ if (!instanceFilter.shouldLog(namespace, args)) {
43
+ return;
44
+ }
45
+
46
+ const self = debug;
47
+
48
+ // Set timestamp diffs
49
+ const curr = Number(new Date());
50
+ const ms = curr - (prevTime || curr);
51
+ self.diff = ms;
52
+ self.prev = prevTime || curr;
53
+ self.curr = curr;
54
+ prevTime = curr;
55
+
56
+ args[0] = (createDebug as any).coerce(args[0]);
57
+
58
+ if (typeof args[0] !== 'string') {
59
+ // Inspect with %O
60
+ args.unshift('%O');
61
+ }
62
+
63
+ // Apply formatters
64
+ let index = 0;
65
+ args[0] = args[0].replace(/%([a-zA-Z%])/g, (match: string, format: string) => {
66
+ // Handle escaped %
67
+ if (match === '%%') {
68
+ return '%';
69
+ }
70
+ index++;
71
+ const formatter = (createDebug as any).formatters[format];
72
+ if (typeof formatter === 'function') {
73
+ const val = args[index];
74
+ match = formatter.call(self, val);
75
+ // Remove the inlined argument
76
+ args.splice(index, 1);
77
+ index--;
78
+ }
79
+ return match;
80
+ });
81
+
82
+ // Apply environment-specific formatting
83
+ (createDebug as any).formatArgs.call(self, args);
84
+
85
+ const logFn = self.log || (createDebug as any).log;
86
+ logFn.apply(self, args);
87
+ }
88
+
89
+ debug.namespace = namespace;
90
+ debug.useColors = options.useColors ?? (createDebug as any).useColors();
91
+ debug.color = options.color ?? (createDebug as any).selectColor(namespace);
92
+ debug.diff = 0;
93
+ debug.prev = 0;
94
+ debug.curr = 0;
95
+ debug.metadata = options.metadata || {};
96
+ debug.log = options.log || env.log;
97
+
98
+ debug.extend = function (ns: string, delimiter?: string): Debugger {
99
+ const sep = typeof delimiter === 'undefined' ? ':' : delimiter;
100
+ const newDebug = createDebug(this.namespace + sep + ns);
101
+ newDebug.log = this.log;
102
+ return newDebug;
103
+ };
104
+
105
+ debug.destroy = (createDebug as any).destroy;
106
+
107
+ debug.setFilter = function (filter: FilterOptions) {
108
+ instanceFilter.setOptions(filter);
109
+ };
110
+
111
+ debug.setMetadata = function (key: string, value: any) {
112
+ this.metadata[key] = value;
113
+ };
114
+
115
+ debug.getMetadata = function (key: string) {
116
+ return this.metadata[key];
117
+ };
118
+
119
+ Object.defineProperty(debug, 'enabled', {
120
+ enumerable: true,
121
+ configurable: false,
122
+ get(): boolean {
123
+ if (enableOverride !== null) {
124
+ return enableOverride;
125
+ }
126
+ if (namespacesCache !== (createDebug as any).namespaces) {
127
+ namespacesCache = (createDebug as any).namespaces;
128
+ enabledCache = (createDebug as any).enabled(namespace);
129
+ }
130
+ return enabledCache;
131
+ },
132
+ set(v: boolean) {
133
+ enableOverride = v;
134
+ },
135
+ });
136
+
137
+ // Environment-specific initialization
138
+ if (typeof (createDebug as any).init === 'function') {
139
+ (createDebug as any).init(debug);
140
+ }
141
+
142
+ return debug as Debugger;
143
+ }
144
+
145
+ // Attach properties from environment config
146
+ (createDebug as any).names = [];
147
+ (createDebug as any).skips = [];
148
+ (createDebug as any).formatters = {};
149
+ (createDebug as any).namespaces = '';
150
+
151
+ // Copy environment-specific properties
152
+ Object.keys(env).forEach((key) => {
153
+ (createDebug as any)[key] = (env as any)[key];
154
+ });
155
+
156
+ createDebug.humanize = ms;
157
+
158
+ /**
159
+ * Select a color for a namespace
160
+ */
161
+ (createDebug as any).selectColor = function (namespace: string): string | number {
162
+ let hash = 0;
163
+ for (let i = 0; i < namespace.length; i++) {
164
+ hash = (hash << 5) - hash + namespace.charCodeAt(i);
165
+ hash |= 0; // Convert to 32bit integer
166
+ }
167
+ return (createDebug as any).colors[Math.abs(hash) % (createDebug as any).colors.length];
168
+ };
169
+
170
+ /**
171
+ * Enable namespaces
172
+ */
173
+ (createDebug as any).enable = function (namespaces: string): void {
174
+ (createDebug as any).save(namespaces);
175
+ (createDebug as any).namespaces = namespaces;
176
+
177
+ (createDebug as any).names = [];
178
+ (createDebug as any).skips = [];
179
+
180
+ const split = (typeof namespaces === 'string' ? namespaces : '')
181
+ .trim()
182
+ .replace(/\s+/g, ',')
183
+ .split(',')
184
+ .filter(Boolean);
185
+
186
+ for (const ns of split) {
187
+ if (ns[0] === '-') {
188
+ (createDebug as any).skips.push(ns.slice(1));
189
+ } else {
190
+ (createDebug as any).names.push(ns);
191
+ }
192
+ }
193
+ };
194
+
195
+ /**
196
+ * Disable all namespaces
197
+ */
198
+ (createDebug as any).disable = function (): string {
199
+ const namespaces = [
200
+ ...(createDebug as any).names,
201
+ ...(createDebug as any).skips.map((ns: string) => '-' + ns),
202
+ ].join(',');
203
+ (createDebug as any).enable('');
204
+ return namespaces;
205
+ };
206
+
207
+ /**
208
+ * Check if a namespace is enabled
209
+ */
210
+ (createDebug as any).enabled = function (name: string): boolean {
211
+ // Check skips first
212
+ for (const skip of (createDebug as any).skips) {
213
+ if (matchesTemplate(name, skip)) {
214
+ return false;
215
+ }
216
+ }
217
+
218
+ // Check names
219
+ for (const ns of (createDebug as any).names) {
220
+ if (matchesTemplate(name, ns)) {
221
+ return true;
222
+ }
223
+ }
224
+
225
+ return false;
226
+ };
227
+
228
+ /**
229
+ * Coerce a value
230
+ */
231
+ (createDebug as any).coerce = function (val: any): any {
232
+ if (val instanceof Error) {
233
+ return val.stack || val.message;
234
+ }
235
+ return val;
236
+ };
237
+
238
+ /**
239
+ * Destroy (deprecated)
240
+ */
241
+ (createDebug as any).destroy = function (): void {
242
+ console.warn(
243
+ 'Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version.'
244
+ );
245
+ };
246
+
247
+ /**
248
+ * Set global filter options
249
+ */
250
+ (createDebug as any).setGlobalFilter = function (filter: FilterOptions) {
251
+ globalFilter.setOptions(filter);
252
+ };
253
+
254
+ /**
255
+ * Get global filter options
256
+ */
257
+ (createDebug as any).getGlobalFilter = function () {
258
+ return globalFilter.getOptions();
259
+ };
260
+
261
+ // Load saved namespaces
262
+ (createDebug as any).enable((createDebug as any).load());
263
+
264
+ return createDebug as any as DebugFactory;
265
+ }
266
+
267
+ /**
268
+ * Match a namespace against a template with wildcards
269
+ */
270
+ function matchesTemplate(search: string, template: string): boolean {
271
+ let searchIndex = 0;
272
+ let templateIndex = 0;
273
+ let starIndex = -1;
274
+ let matchIndex = 0;
275
+
276
+ while (searchIndex < search.length) {
277
+ if (
278
+ templateIndex < template.length &&
279
+ (template[templateIndex] === search[searchIndex] || template[templateIndex] === '*')
280
+ ) {
281
+ if (template[templateIndex] === '*') {
282
+ starIndex = templateIndex;
283
+ matchIndex = searchIndex;
284
+ templateIndex++;
285
+ } else {
286
+ searchIndex++;
287
+ templateIndex++;
288
+ }
289
+ } else if (starIndex !== -1) {
290
+ templateIndex = starIndex + 1;
291
+ matchIndex++;
292
+ searchIndex = matchIndex;
293
+ } else {
294
+ return false;
295
+ }
296
+ }
297
+
298
+ // Handle trailing wildcards
299
+ while (templateIndex < template.length && template[templateIndex] === '*') {
300
+ templateIndex++;
301
+ }
302
+
303
+ return templateIndex === template.length;
304
+ }
305
+
306
+ export { matchesTemplate };
package/src/filter.ts ADDED
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Advanced filtering capabilities for debug-utility
3
+ */
4
+
5
+ import { FilterOptions, FilterPredicate } from './types';
6
+
7
+ /**
8
+ * Filter manager class for handling complex filtering logic
9
+ */
10
+ export class FilterManager {
11
+ private options: FilterOptions;
12
+
13
+ constructor(options: FilterOptions = {}) {
14
+ this.options = {
15
+ enabled: true,
16
+ patterns: [],
17
+ predicates: [],
18
+ tags: [],
19
+ include: [],
20
+ exclude: [],
21
+ ...options,
22
+ };
23
+ }
24
+
25
+ /**
26
+ * Update filter options
27
+ */
28
+ setOptions(options: Partial<FilterOptions>): void {
29
+ this.options = { ...this.options, ...options };
30
+ }
31
+
32
+ /**
33
+ * Get current filter options
34
+ */
35
+ getOptions(): FilterOptions {
36
+ return { ...this.options };
37
+ }
38
+
39
+ /**
40
+ * Check if a namespace passes the filter
41
+ */
42
+ shouldLog(namespace: string, args: any[] = []): boolean {
43
+ if (!this.options.enabled) {
44
+ return true;
45
+ }
46
+
47
+ // Check exclude list first (highest priority)
48
+ if (this.options.exclude && this.options.exclude.length > 0) {
49
+ for (const pattern of this.options.exclude) {
50
+ if (this.matchPattern(namespace, pattern)) {
51
+ return false;
52
+ }
53
+ }
54
+ }
55
+
56
+ // Check include list
57
+ if (this.options.include && this.options.include.length > 0) {
58
+ let included = false;
59
+ for (const pattern of this.options.include) {
60
+ if (this.matchPattern(namespace, pattern)) {
61
+ included = true;
62
+ break;
63
+ }
64
+ }
65
+ if (!included) {
66
+ return false;
67
+ }
68
+ }
69
+
70
+ // Check regex patterns
71
+ if (this.options.patterns && this.options.patterns.length > 0) {
72
+ let matched = false;
73
+ for (const pattern of this.options.patterns) {
74
+ if (pattern.test(namespace)) {
75
+ matched = true;
76
+ break;
77
+ }
78
+ }
79
+ if (!matched) {
80
+ return false;
81
+ }
82
+ }
83
+
84
+ // Check custom predicates
85
+ if (this.options.predicates && this.options.predicates.length > 0) {
86
+ for (const predicate of this.options.predicates) {
87
+ if (!predicate(namespace, ...args)) {
88
+ return false;
89
+ }
90
+ }
91
+ }
92
+
93
+ // Check tags (if first argument is an object with tags)
94
+ if (this.options.tags && this.options.tags.length > 0) {
95
+ if (args.length > 0 && typeof args[0] === 'object' && args[0].tags) {
96
+ const argTags = Array.isArray(args[0].tags) ? args[0].tags : [args[0].tags];
97
+ const hasMatchingTag = argTags.some((tag: string) =>
98
+ this.options.tags!.includes(tag)
99
+ );
100
+ if (!hasMatchingTag) {
101
+ return false;
102
+ }
103
+ }
104
+ }
105
+
106
+ return true;
107
+ }
108
+
109
+ /**
110
+ * Match a namespace against a pattern (supports wildcards)
111
+ */
112
+ private matchPattern(namespace: string, pattern: string): boolean {
113
+ // Convert wildcard pattern to regex
114
+ const regexPattern = pattern
115
+ .replace(/[.+?^${}()|[\]\\]/g, '\\$&') // Escape special chars
116
+ .replace(/\*/g, '.*'); // Replace * with .*
117
+
118
+ const regex = new RegExp(`^${regexPattern}$`);
119
+ return regex.test(namespace);
120
+ }
121
+
122
+ /**
123
+ * Add a custom predicate filter
124
+ */
125
+ addPredicate(predicate: FilterPredicate): void {
126
+ if (!this.options.predicates) {
127
+ this.options.predicates = [];
128
+ }
129
+ this.options.predicates.push(predicate);
130
+ }
131
+
132
+ /**
133
+ * Add a regex pattern filter
134
+ */
135
+ addPattern(pattern: RegExp): void {
136
+ if (!this.options.patterns) {
137
+ this.options.patterns = [];
138
+ }
139
+ this.options.patterns.push(pattern);
140
+ }
141
+
142
+ /**
143
+ * Add include pattern
144
+ */
145
+ addInclude(pattern: string): void {
146
+ if (!this.options.include) {
147
+ this.options.include = [];
148
+ }
149
+ this.options.include.push(pattern);
150
+ }
151
+
152
+ /**
153
+ * Add exclude pattern
154
+ */
155
+ addExclude(pattern: string): void {
156
+ if (!this.options.exclude) {
157
+ this.options.exclude = [];
158
+ }
159
+ this.options.exclude.push(pattern);
160
+ }
161
+
162
+ /**
163
+ * Add tag filter
164
+ */
165
+ addTag(tag: string): void {
166
+ if (!this.options.tags) {
167
+ this.options.tags = [];
168
+ }
169
+ this.options.tags.push(tag);
170
+ }
171
+
172
+ /**
173
+ * Clear all filters
174
+ */
175
+ clear(): void {
176
+ this.options = {
177
+ enabled: true,
178
+ patterns: [],
179
+ predicates: [],
180
+ tags: [],
181
+ include: [],
182
+ exclude: [],
183
+ };
184
+ }
185
+
186
+ /**
187
+ * Enable filtering
188
+ */
189
+ enable(): void {
190
+ this.options.enabled = true;
191
+ }
192
+
193
+ /**
194
+ * Disable filtering
195
+ */
196
+ disable(): void {
197
+ this.options.enabled = false;
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Global filter instance
203
+ */
204
+ export const globalFilter = new FilterManager();
package/src/index.ts ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Main entry point - detects environment and loads appropriate implementation
3
+ */
4
+
5
+ /**
6
+ * Detect Electron renderer / nwjs process, which is node, but should
7
+ * be treated as a browser.
8
+ */
9
+
10
+ if (
11
+ typeof process === 'undefined' ||
12
+ (process as any).type === 'renderer' ||
13
+ (process as any).browser === true ||
14
+ (process as any).__nwjs
15
+ ) {
16
+ module.exports = require('./browser');
17
+ } else {
18
+ module.exports = require('./node');
19
+ }
20
+
21
+ // Export types for TypeScript users
22
+ export * from './types';
23
+ export { FilterManager, globalFilter } from './filter';
package/src/node.ts ADDED
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Node.js implementation of debug-utility
3
+ */
4
+
5
+ import * as tty from 'tty';
6
+ import * as util from 'util';
7
+ import { setup } from './common';
8
+ import { EnvironmentConfig, Debugger } from './types';
9
+
10
+ /**
11
+ * Colors for Node.js (ANSI color codes)
12
+ */
13
+ const colors = [
14
+ 20, 21, 26, 27, 32, 33, 38, 39, 40, 41, 42, 43, 44, 45, 56, 57, 62, 63, 68, 69, 74, 75, 76, 77,
15
+ 78, 79, 80, 81, 92, 93, 98, 99, 112, 113, 128, 129, 134, 135, 148, 149, 160, 161, 162, 163, 164,
16
+ 165, 166, 167, 168, 169, 170, 171, 172, 173, 178, 179, 184, 185, 196, 197, 198, 199, 200, 201,
17
+ 202, 203, 204, 205, 206, 207, 208, 209, 214, 215, 220, 221,
18
+ ];
19
+
20
+ /**
21
+ * Check if supports-color is available (optional dependency)
22
+ */
23
+ try {
24
+ const supportsColor = require('supports-color');
25
+ if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) {
26
+ // Use extended color palette if available
27
+ }
28
+ } catch (e) {
29
+ // supports-color is optional
30
+ }
31
+
32
+ /**
33
+ * Build inspect options from environment variables
34
+ */
35
+ const inspectOpts = Object.keys(process.env)
36
+ .filter((key) => /^debug_/i.test(key))
37
+ .reduce((obj: Record<string, any>, key: string) => {
38
+ // Convert DEBUG_COLORS to colors
39
+ const prop = key
40
+ .substring(6)
41
+ .toLowerCase()
42
+ .replace(/_([a-z])/g, (_, k) => k.toUpperCase());
43
+
44
+ // Coerce string value
45
+ let val: any = process.env[key];
46
+ if (/^(yes|on|true|enabled)$/i.test(val)) {
47
+ val = true;
48
+ } else if (/^(no|off|false|disabled)$/i.test(val)) {
49
+ val = false;
50
+ } else if (val === 'null') {
51
+ val = null;
52
+ } else {
53
+ val = Number(val);
54
+ }
55
+
56
+ obj[prop] = val;
57
+ return obj;
58
+ }, {});
59
+
60
+ /**
61
+ * Check if colors should be used
62
+ */
63
+ function useColors(): boolean {
64
+ if ('colors' in inspectOpts) {
65
+ return Boolean(inspectOpts.colors);
66
+ }
67
+ return tty.isatty(process.stderr.fd);
68
+ }
69
+
70
+ /**
71
+ * Format arguments with ANSI colors
72
+ */
73
+ function formatArgs(this: Debugger, args: any[]): void {
74
+ const { namespace, useColors: shouldUseColors } = this;
75
+
76
+ if (shouldUseColors) {
77
+ const c = this.color as number;
78
+ const colorCode = '\u001B[3' + (c < 8 ? c : '8;5;' + c);
79
+ const prefix = ` ${colorCode};1m${namespace} \u001B[0m`;
80
+
81
+ args[0] = prefix + args[0].split('\n').join('\n' + prefix);
82
+ args.push(colorCode + 'm+' + ms(this.diff) + '\u001B[0m');
83
+ } else {
84
+ args[0] = getDate() + namespace + ' ' + args[0];
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Get formatted date
90
+ */
91
+ function getDate(): string {
92
+ if (inspectOpts.hideDate) {
93
+ return '';
94
+ }
95
+ return new Date().toISOString() + ' ';
96
+ }
97
+
98
+ /**
99
+ * Log to stderr
100
+ */
101
+ function log(...args: any[]): void {
102
+ process.stderr.write(util.formatWithOptions(inspectOpts, ...args) + '\n');
103
+ }
104
+
105
+ /**
106
+ * Save namespaces to environment
107
+ */
108
+ function save(namespaces: string): void {
109
+ if (namespaces) {
110
+ process.env.DEBUG = namespaces;
111
+ } else {
112
+ delete process.env.DEBUG;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Load namespaces from environment
118
+ */
119
+ function load(): string {
120
+ return process.env.DEBUG || '';
121
+ }
122
+
123
+ /**
124
+ * Destroy function (deprecated)
125
+ */
126
+ function destroy(): void {
127
+ console.warn(
128
+ 'Instance method `debug.destroy()` is deprecated and no longer does anything.'
129
+ );
130
+ }
131
+
132
+ /**
133
+ * Initialize a debug instance (optional)
134
+ */
135
+ function init(debug: Debugger): void {
136
+ // Initialization logic can be added here if needed
137
+ (debug as any).inspectOpts = inspectOpts;
138
+ }
139
+
140
+ // Import ms for humanizing time
141
+ import ms = require('ms');
142
+
143
+ /**
144
+ * Create environment config for Node.js
145
+ */
146
+ const nodeEnv: EnvironmentConfig = {
147
+ formatArgs,
148
+ save,
149
+ load,
150
+ useColors,
151
+ colors,
152
+ log,
153
+ inspectOpts,
154
+ init,
155
+ destroy,
156
+ };
157
+
158
+ /**
159
+ * Export the debug factory
160
+ */
161
+ export = setup(nodeEnv);