@crimsonsunset/jsg-logger 1.7.0 → 1.7.2

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.
Files changed (4) hide show
  1. package/CHANGELOG.md +110 -5
  2. package/index.d.ts +192 -0
  3. package/index.js +77 -11
  4. package/package.json +8 -2
package/CHANGELOG.md CHANGED
@@ -5,7 +5,7 @@ All notable changes to the JSG Logger project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [1.6.0] - 2025-01-XX 🎯 **Config-Driven Tree-Shaking & Enhanced Logging**
8
+ ## [1.7.0] - 2025-01-XX 🎯 **Config-Driven Tree-Shaking & Enhanced Logging**
9
9
 
10
10
  ### Added
11
11
  - **Config-Driven Tree-Shaking** - DevTools panel tree-shaking now based on default config at module load time
@@ -56,13 +56,118 @@ No breaking changes. Existing code continues to work.
56
56
  - No special Vite config needed for production builds
57
57
  - npm link users: add `server.fs.allow: ['..']` to Vite config for dev
58
58
 
59
- ## [Unreleased]
59
+ ## [1.7.1] - 2025-11-05 🎯 **TypeScript Support & Zero-Optional-Chaining API**
60
60
 
61
- ## [1.5.2] - 2025-10-25 🐛 **PATCH: CLI Formatter Custom Component Display**
61
+ ### Added
62
+ - **TypeScript Definitions** - Full TypeScript support with `index.d.ts`
63
+ - Complete type definitions for all logger interfaces
64
+ - `LoggerInstance`, `LoggerComponents`, `LoggerInstanceType` interfaces
65
+ - Proper exports configuration in `package.json` with `types` field
66
+ - **Safe Component Getters** - Component getters initialized in constructor
67
+ - `_initializeSafeComponentGetters()` ensures components accessible before initialization
68
+ - Common components (reactComponents, astroComponents, etc.) always available
69
+ - Returns no-op logger factories if logger not initialized yet
70
+
71
+ ### Changed
72
+ - **getComponent Always Available** - `getComponent` is now non-optional in TypeScript types
73
+ - Removed `?` optional marker from `getComponent` in `LoggerInstanceType`
74
+ - Always returns a logger instance (no-op if not initialized)
75
+ - Consumers can call `loggerInstance.getComponent('componentName')` without optional chaining
76
+ - **Fallback Logger Enhancement** - `createFallbackLogger()` now includes `getComponent`
77
+ - Ensures `getComponent` exists even when initialization fails
78
+ - Returns no-op logger factory for safe fallback behavior
79
+ - **Enhanced getComponent Safety** - Improved error handling and edge cases
80
+ - Returns no-op logger if instance not initialized
81
+ - Returns no-op logger if logger creation fails
82
+ - Prevents crashes when logger unavailable
62
83
 
63
84
  ### Fixed
64
- - **CLI formatter custom component names** - Formatter was using hardcoded `COMPONENT_SCHEME` instead of `configManager`, causing custom component names to display as `[JSG-CORE]` instead of user-defined names like `[SETUP]`
65
- - **Browser formatter** - Removed redundant component name transformation
85
+ - **Optional Chaining Burden** - Eliminated need for `?.` optional chaining in consumer code
86
+ - Consumers can now use `loggerInstance.getComponent('componentName').debug(...)` directly
87
+ - No more `loggerInstance?.getComponent?.('componentName')?.debug(...)` chains
88
+ - TypeScript types guarantee `getComponent` always exists
89
+
90
+ ### Technical Details
91
+ - **File**: `index.d.ts` (NEW)
92
+ - Complete TypeScript definitions for the logger package
93
+ - Non-optional `getComponent` method signature
94
+ - Proper interface documentation
95
+
96
+ - **File**: `index.js`
97
+ - Added `_initializeSafeComponentGetters()` method called in constructor
98
+ - Enhanced `getComponent()` to always return logger (no-op fallback)
99
+ - Added `_createNoOpLogger()` private method for safe fallbacks
100
+ - Updated `createFallbackLogger()` to include `getComponent`
101
+ - Modified `_createAutoDiscoveryGetters()` to preserve safe getters
102
+
103
+ - **File**: `package.json`
104
+ - Added `"types": "./index.d.ts"` field
105
+ - Added `index.d.ts` to `files` array
106
+ - Updated `exports` to include proper TypeScript types export
107
+
108
+ ### Migration Notes
109
+ No breaking changes. Existing code continues to work, but optional chaining is now optional (pun intended).
110
+
111
+ **For consumers:**
112
+ - Can now use `loggerInstance.getComponent('componentName')` without `?.`
113
+ - TypeScript users get full type safety and autocomplete
114
+ - No-op logger returned if logger not initialized (safe to call)
115
+ - Recommended: Use `getInstanceSync()` for synchronous access instead of `getInstance()` when possible
116
+
117
+ ## [1.7.0] - 2025-01-XX 🎯 **Config-Driven Tree-Shaking & Enhanced Logging**
118
+
119
+ ### Added
120
+ - **Config-Driven Tree-Shaking** - DevTools panel tree-shaking now based on default config at module load time
121
+ - Tree-shaking determined by `defaultConfig.devtools.enabled` (default: `false`)
122
+ - When disabled, devtools code completely tree-shaken (zero bundle impact)
123
+ - When enabled via runtime config, loads dynamically on demand
124
+ - Static import analysis allows bundlers to eliminate unused code paths
125
+ - **Comprehensive DevTools Logging** - Added detailed logging throughout DevTools lifecycle
126
+ - Module load: logs default config tree-shaking status
127
+ - Config loading: logs when devtools enabled/disabled via user config
128
+ - DevTools activation: logs pre-load status, dynamic loading, and initialization steps
129
+ - Config merge: logs devtools status changes between defaults and user config
130
+
131
+ ### Changed
132
+ - **DevTools Import Strategy** - Simplified to relative path imports
133
+ - Removed complex Function constructor + package export path logic
134
+ - Uses simple relative path: `./devtools/dist/panel-entry.js`
135
+ - Works in production builds, requires `server.fs.allow: ['..']` for npm link dev
136
+ - **Config Loading Logging** - Enhanced visibility into config loading process
137
+ - Logs config source (file path vs inline object)
138
+ - Logs devtools status before/after config merge
139
+ - Logs initialization start with config source information
140
+
141
+ ### Fixed
142
+ - **Tree-Shaking Detection** - Bundlers can now properly analyze and eliminate devtools code when disabled
143
+ - **DevTools Pre-loading** - Non-blocking pre-load when default config enables devtools
144
+ - **Runtime Config Override** - Dynamic loading works correctly when runtime config enables devtools but default disabled
145
+
146
+ ### Technical Details
147
+ - **File**: `index.js`
148
+ - Added conditional static import based on `defaultConfig.devtools.enabled`
149
+ - Lazy initialization pattern to avoid top-level await
150
+ - Enhanced `enableDevPanel()` with comprehensive logging
151
+ - Pre-loads devtools module when default config enables it
152
+
153
+ - **File**: `config/config-manager.js`
154
+ - Added logging for config loading (file vs inline)
155
+ - Added devtools status logging before/after config merge
156
+ - Enhanced visibility into config changes
157
+
158
+ ### Migration Notes
159
+ No breaking changes. Existing code continues to work.
160
+
161
+ **For consumers:**
162
+ - DevTools code tree-shaken by default (zero bundle impact)
163
+ - Enable via runtime config: `{ devtools: { enabled: true } }`
164
+ - DevTools loads dynamically when `enableDevPanel()` called
165
+ - No special Vite config needed for production builds
166
+ - npm link users: add `server.fs.allow: ['..']` to Vite config for dev
167
+
168
+ ## [Unreleased]
169
+
170
+ ## [1.5.2] - 2025-10-25 🐛 **PATCH: CLI Formatter Custom Component Display**
66
171
 
67
172
  ### Technical Changes
68
173
  - `formatters/cli-formatter.js` - Use `configManager.getComponentConfig()` instead of static `COMPONENT_SCHEME`
package/index.d.ts ADDED
@@ -0,0 +1,192 @@
1
+ /**
2
+ * TypeScript definitions for @crimsonsunset/jsg-logger
3
+ */
4
+
5
+ export interface LoggerInstance {
6
+ info: (message: string, data?: any) => void;
7
+ debug: (message: string, data?: any) => void;
8
+ warn: (message: string, data?: any) => void;
9
+ error: (message: string, data?: any) => void;
10
+ fatal: (message: string, data?: any) => void;
11
+ trace: (message: string, data?: any) => void;
12
+ [key: string]: any;
13
+ }
14
+
15
+ export interface LoggerComponents {
16
+ /**
17
+ * Component loggers - factory functions that ALWAYS return LoggerInstance
18
+ * Components return no-op logger if not initialized or unavailable
19
+ * Safe to call without null checks: components.reactComponents?.().info(...)
20
+ */
21
+ reactComponents?: () => LoggerInstance;
22
+ astroComponents?: () => LoggerInstance;
23
+ astroBuild?: () => LoggerInstance;
24
+ astroIntegration?: () => LoggerInstance;
25
+ contentProcessing?: () => LoggerInstance;
26
+ textUtils?: () => LoggerInstance;
27
+ dateUtils?: () => LoggerInstance;
28
+ pages?: () => LoggerInstance;
29
+ config?: () => LoggerInstance;
30
+ seo?: () => LoggerInstance;
31
+ performance?: () => LoggerInstance;
32
+ devServer?: () => LoggerInstance;
33
+ webComponents?: () => LoggerInstance;
34
+ core?: () => LoggerInstance;
35
+ api?: () => LoggerInstance;
36
+ ui?: () => LoggerInstance;
37
+ database?: () => LoggerInstance;
38
+ test?: () => LoggerInstance;
39
+ preact?: () => LoggerInstance;
40
+ auth?: () => LoggerInstance;
41
+ analytics?: () => LoggerInstance;
42
+ websocket?: () => LoggerInstance;
43
+ notification?: () => LoggerInstance;
44
+ router?: () => LoggerInstance;
45
+ cache?: () => LoggerInstance;
46
+ /**
47
+ * Dynamic component access - always returns a logger factory
48
+ * The factory function always returns LoggerInstance (no-op if unavailable)
49
+ */
50
+ [key: string]: (() => LoggerInstance) | undefined;
51
+ }
52
+
53
+ export interface JSGLoggerConfig {
54
+ configPath?: string;
55
+ config?: Record<string, any>;
56
+ devtools?: {
57
+ enabled?: boolean;
58
+ };
59
+ [key: string]: any;
60
+ }
61
+
62
+ export interface LoggerControls {
63
+ setLevel?: (component: string, level: string) => void;
64
+ getLevel?: (component: string) => string | undefined;
65
+ listComponents?: () => string[];
66
+ enableDebugMode?: () => void;
67
+ enableTraceMode?: () => void;
68
+ addFileOverride?: (filePath: string, overrideConfig: Record<string, any>) => void;
69
+ removeFileOverride?: (filePath: string) => void;
70
+ listFileOverrides?: () => string[];
71
+ setTimestampMode?: (mode: string) => void;
72
+ getTimestampMode?: () => string;
73
+ getTimestampModes?: () => string[];
74
+ setDisplayOption?: (option: string, enabled: boolean) => void;
75
+ getDisplayConfig?: () => Record<string, boolean>;
76
+ toggleDisplayOption?: (option: string) => void;
77
+ getStats?: () => any;
78
+ subscribe?: (callback: Function) => void;
79
+ clearLogs?: () => void;
80
+ getConfigSummary?: () => any;
81
+ setComponentLevel?: (component: string, level: string) => void;
82
+ getComponentLevel?: (component: string) => string | undefined;
83
+ enableDevPanel?: () => Promise<any>;
84
+ disableDevPanel?: () => boolean;
85
+ refresh?: () => void;
86
+ reset?: () => void;
87
+ [key: string]: any;
88
+ }
89
+
90
+ export interface LoggerConfig {
91
+ environment?: string;
92
+ components?: Record<string, any>;
93
+ summary?: any;
94
+ }
95
+
96
+ export interface LoggerInstanceType {
97
+ /**
98
+ * Direct access to component loggers
99
+ */
100
+ [componentName: string]: LoggerInstance | any;
101
+
102
+ /**
103
+ * Auto-discovery convenience getters (factory functions)
104
+ */
105
+ components?: LoggerComponents;
106
+
107
+ /**
108
+ * Get a specific component logger - always returns a logger instance
109
+ * Returns no-op logger if component unavailable or logger not initialized
110
+ * Always available - never undefined
111
+ */
112
+ getComponent: (componentName: string) => LoggerInstance;
113
+
114
+ /**
115
+ * Create a logger for a specific component
116
+ */
117
+ createLogger?: (componentName: string) => LoggerInstance;
118
+
119
+ /**
120
+ * Configuration and debugging info
121
+ */
122
+ config?: LoggerConfig;
123
+
124
+ /**
125
+ * Configuration manager instance
126
+ */
127
+ configManager?: any;
128
+
129
+ /**
130
+ * Log store instance
131
+ */
132
+ logStore?: any;
133
+
134
+ /**
135
+ * Enhanced runtime controls
136
+ */
137
+ controls?: LoggerControls;
138
+
139
+ /**
140
+ * Get singleton instance (async)
141
+ */
142
+ getInstance?: (config?: JSGLoggerConfig) => Promise<LoggerInstanceType> | LoggerInstanceType;
143
+
144
+ /**
145
+ * Get singleton instance (sync)
146
+ */
147
+ getInstanceSync?: (config?: JSGLoggerConfig) => LoggerInstanceType;
148
+
149
+ /**
150
+ * Static performance logging utility
151
+ */
152
+ logPerformance?: (label: string, startTime: number, component?: string) => Promise<number>;
153
+ }
154
+
155
+ export interface JSGLogger {
156
+ /**
157
+ * Get singleton instance with auto-initialization
158
+ * @param config - Initialization options (only used on first call)
159
+ * @returns Enhanced logger exports with controls API
160
+ *
161
+ * Note: Returns Promise if initialization is needed, sync value if already initialized.
162
+ * Use `await` to handle both cases safely.
163
+ *
164
+ * @example
165
+ * ```ts
166
+ * const logger = await JSGLogger.getInstance({ configPath: 'logger-config.json' });
167
+ * logger.components?.reactComponents?.().info('Hello');
168
+ * ```
169
+ */
170
+ getInstance(config?: JSGLoggerConfig): Promise<LoggerInstanceType> | LoggerInstanceType;
171
+
172
+ /**
173
+ * Get singleton instance synchronously (for environments without async support)
174
+ * @param config - Initialization options (only used on first call)
175
+ * @returns Enhanced logger exports with controls API
176
+ */
177
+ getInstanceSync(config?: JSGLoggerConfig): LoggerInstanceType;
178
+
179
+ /**
180
+ * Static performance logging utility
181
+ * @param label - Performance label
182
+ * @param startTime - Start time from performance.now()
183
+ * @param component - Optional component name (defaults to 'performance')
184
+ * @returns Duration in milliseconds
185
+ */
186
+ logPerformance(label: string, startTime: number, component?: string): Promise<number>;
187
+ }
188
+
189
+ declare const JSGLogger: JSGLogger;
190
+ export default JSGLogger;
191
+ export { JSGLogger };
192
+
package/index.js CHANGED
@@ -53,6 +53,9 @@ class JSGLogger {
53
53
  this.environment = null; // Will be set after config loads
54
54
  this.initialized = false;
55
55
  this.components = {}; // Auto-discovery getters
56
+
57
+ // Initialize component getters with safe factories that always return loggers
58
+ this._initializeSafeComponentGetters();
56
59
  }
57
60
 
58
61
  /**
@@ -637,9 +640,13 @@ class JSGLogger {
637
640
  fatal: console.error
638
641
  };
639
642
 
643
+ // No-op logger factory for getComponent
644
+ const noOpLogger = () => fallback;
645
+
640
646
  return {
641
647
  core: fallback,
642
648
  createLogger: () => fallback,
649
+ getComponent: noOpLogger,
643
650
  config: {environment: 'fallback'},
644
651
  logStore: {getRecent: () => [], clear: () => {}},
645
652
  controls: {
@@ -653,20 +660,46 @@ class JSGLogger {
653
660
  }
654
661
 
655
662
  /**
656
- * Create auto-discovery getters for easy component access
657
- * Supports both kebab-case (original) and camelCase naming
663
+ * Initialize safe component getters that always return logger factories
664
+ * This ensures components can be accessed even before initialization
658
665
  * @private
659
666
  */
660
- _createAutoDiscoveryGetters() {
661
- this.components = {};
667
+ _initializeSafeComponentGetters() {
668
+ // Create safe getters for common components that always return a logger factory
669
+ // These will work even if logger isn't initialized yet
670
+ const commonComponents = [
671
+ 'reactComponents', 'react-components',
672
+ 'astroComponents', 'astro-components',
673
+ 'astroBuild', 'astro-build',
674
+ 'astroIntegration', 'astro-integration',
675
+ 'contentProcessing', 'content-processing',
676
+ 'textUtils', 'text-utils',
677
+ 'dateUtils', 'date-utils',
678
+ 'pages', 'webComponents', 'web-components',
679
+ 'core', 'api', 'ui', 'database', 'test', 'preact',
680
+ 'auth', 'analytics', 'performance', 'websocket',
681
+ 'notification', 'router', 'cache',
682
+ 'config', 'seo', 'devServer', 'dev-server'
683
+ ];
684
+
685
+ commonComponents.forEach(name => {
686
+ // Create getter that always returns a logger (no-op if not initialized)
687
+ this.components[name] = () => this.getComponent(name);
688
+ });
689
+ }
662
690
 
691
+ _createAutoDiscoveryGetters() {
692
+ // Don't reset components - preserve safe getters created in constructor
693
+ // Only add getters for newly created loggers
663
694
  Object.keys(this.loggers).forEach(name => {
664
- // Original kebab-case name
665
- this.components[name] = () => this.getComponent(name);
695
+ // Only add if not already exists (preserve safe getters)
696
+ if (!this.components[name]) {
697
+ this.components[name] = () => this.getComponent(name);
698
+ }
666
699
 
667
700
  // camelCase convenience getter
668
701
  const camelName = name.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase());
669
- if (camelName !== name) {
702
+ if (camelName !== name && !this.components[camelName]) {
670
703
  this.components[camelName] = () => this.getComponent(name);
671
704
  }
672
705
  });
@@ -674,12 +707,23 @@ class JSGLogger {
674
707
 
675
708
  /**
676
709
  * Get a specific component logger with auto-creation for custom components
710
+ * Always returns a logger instance (no-op if initialization failed)
677
711
  * @param {string} componentName - Component name to retrieve
678
- * @returns {Object} Logger instance (auto-created if needed)
712
+ * @returns {Object} Logger instance (always returns logger, never undefined)
679
713
  */
680
714
  getComponent(componentName) {
681
- // If logger doesn't exist, auto-create it for custom components
682
- if (!this.loggers[componentName]) {
715
+ // If logger instance isn't initialized, return no-op logger
716
+ if (!this.initialized || !this.loggers) {
717
+ return this._createNoOpLogger(componentName);
718
+ }
719
+
720
+ // If logger exists, return it
721
+ if (this.loggers[componentName]) {
722
+ return this.loggers[componentName];
723
+ }
724
+
725
+ // Try to create logger - if it fails, return no-op logger
726
+ try {
683
727
  // Check if this is a configured component or custom component
684
728
  const hasConfig = configManager.config.components?.[componentName];
685
729
  const hasScheme = COMPONENT_SCHEME[componentName];
@@ -700,9 +744,31 @@ class JSGLogger {
700
744
  if (camelName !== componentName) {
701
745
  this.components[camelName] = () => this.getComponent(componentName);
702
746
  }
747
+
748
+ return this.loggers[componentName];
749
+ } catch (error) {
750
+ // If logger creation fails, return no-op logger to prevent crashes
751
+ console.warn(`[JSG-LOGGER] Failed to create logger for component '${componentName}', using no-op logger:`, error);
752
+ return this._createNoOpLogger(componentName);
703
753
  }
754
+ }
704
755
 
705
- return this.loggers[componentName];
756
+ /**
757
+ * Create a silent no-op logger that can be safely used when logger isn't available
758
+ * @param {string} componentName - Component name (for consistency)
759
+ * @returns {Object} Logger instance with all methods as no-ops
760
+ * @private
761
+ */
762
+ _createNoOpLogger(componentName) {
763
+ // Silent no-op logger - all methods do nothing
764
+ return {
765
+ trace: () => {},
766
+ debug: () => {},
767
+ info: () => {},
768
+ warn: () => {},
769
+ error: () => {},
770
+ fatal: () => {}
771
+ };
706
772
  }
707
773
 
708
774
  /**
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@crimsonsunset/jsg-logger",
3
- "version": "1.7.0",
3
+ "version": "1.7.2",
4
4
  "type": "module",
5
5
  "description": "Multi-environment logger with smart detection, file-level overrides, and beautiful console formatting. Test it live: https://logger.joesangiorgio.com/",
6
6
  "main": "index.js",
7
+ "types": "./index.d.ts",
7
8
  "keywords": [
8
9
  "logging",
9
10
  "pino",
@@ -50,6 +51,7 @@
50
51
  },
51
52
  "files": [
52
53
  "index.js",
54
+ "index.d.ts",
53
55
  "config/",
54
56
  "formatters/",
55
57
  "stores/",
@@ -63,7 +65,11 @@
63
65
  "LICENSE"
64
66
  ],
65
67
  "exports": {
66
- ".": "./index.js",
68
+ ".": {
69
+ "import": "./index.js",
70
+ "require": "./index.js",
71
+ "types": "./index.d.ts"
72
+ },
67
73
  "./config": "./config/config-manager.js",
68
74
  "./config/manager": "./config/config-manager.js",
69
75
  "./config/schemes": "./config/component-schemes.js",