@crimsonsunset/jsg-logger 1.1.1 → 1.1.3

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.
@@ -24,26 +24,18 @@ export class ConfigManager {
24
24
  let externalConfig = {};
25
25
 
26
26
  if (typeof configSource === 'string') {
27
- // Load from file path
28
- if (configSource.startsWith('./') || configSource.startsWith('../')) {
29
- // Relative path - attempt to load
30
- try {
31
- const response = await fetch(configSource);
32
- if (response.ok) {
33
- externalConfig = await response.json();
34
- this.loadedPaths.push(configSource);
35
- }
36
- } catch (error) {
37
- console.warn(`Failed to load config from ${configSource}:`, error.message);
38
- }
39
- }
27
+ // Load from file path - handle all path formats
28
+ externalConfig = await this._loadConfigFromPath(configSource);
40
29
  } else if (typeof configSource === 'object') {
41
30
  // Direct config object
42
31
  externalConfig = configSource;
43
32
  }
44
33
 
45
- // Merge configurations
46
- this.config = this.mergeConfigs(this.config, externalConfig);
34
+ // Normalize external config to match expected structure
35
+ const normalizedConfig = this._normalizeConfigStructure(externalConfig);
36
+
37
+ // Merge configurations - project configs override defaults
38
+ this.config = this.mergeConfigs(this.config, normalizedConfig);
47
39
 
48
40
  return this.config;
49
41
  } catch (error) {
@@ -52,6 +44,260 @@ export class ConfigManager {
52
44
  }
53
45
  }
54
46
 
47
+ /**
48
+ * Load configuration from a file path with environment detection
49
+ * @param {string} configPath - File path to load
50
+ * @returns {Promise<Object>} Configuration object
51
+ * @private
52
+ */
53
+ async _loadConfigFromPath(configPath) {
54
+ try {
55
+ // Normalize path - add ./ prefix if missing for relative paths
56
+ const normalizedPath = this._normalizePath(configPath);
57
+
58
+ // Try different loading strategies based on environment
59
+ let config = null;
60
+
61
+ // Strategy 1: Browser environment with fetch
62
+ if (typeof window !== 'undefined' && typeof fetch !== 'undefined') {
63
+ config = await this._loadConfigBrowser(normalizedPath);
64
+ }
65
+
66
+ // Strategy 2: Node.js environment with dynamic import
67
+ if (!config && typeof process !== 'undefined') {
68
+ config = await this._loadConfigNode(normalizedPath);
69
+ }
70
+
71
+ // Strategy 3: Fallback browser import (for bundlers like Vite)
72
+ if (!config && typeof window !== 'undefined') {
73
+ config = await this._loadConfigBrowserImport(normalizedPath);
74
+ }
75
+
76
+ if (config) {
77
+ this.loadedPaths.push(configPath);
78
+ console.log(`[JSG-LOGGER] Successfully loaded config from: ${configPath}`);
79
+ return config;
80
+ } else {
81
+ console.warn(`[JSG-LOGGER] Could not load config from: ${configPath} - using defaults`);
82
+ return {};
83
+ }
84
+ } catch (error) {
85
+ console.warn(`[JSG-LOGGER] Failed to load config from ${configPath}:`, error.message);
86
+ return {};
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Normalize file path for consistent handling
92
+ * @param {string} path - Original path
93
+ * @returns {string} Normalized path
94
+ * @private
95
+ */
96
+ _normalizePath(path) {
97
+ // Add ./ prefix for relative paths that don't have it
98
+ if (!path.startsWith('./') && !path.startsWith('../') && !path.startsWith('/')) {
99
+ return `./${path}`;
100
+ }
101
+ return path;
102
+ }
103
+
104
+ /**
105
+ * Load config in browser environment using fetch
106
+ * @param {string} path - File path
107
+ * @returns {Promise<Object|null>} Configuration object or null
108
+ * @private
109
+ */
110
+ async _loadConfigBrowser(path) {
111
+ try {
112
+ const response = await fetch(path);
113
+ if (response.ok) {
114
+ return await response.json();
115
+ }
116
+ return null;
117
+ } catch (error) {
118
+ return null;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Load config in Node.js environment
124
+ * @param {string} path - File path
125
+ * @returns {Promise<Object|null>} Configuration object or null
126
+ * @private
127
+ */
128
+ async _loadConfigNode(path) {
129
+ // Only use Node.js APIs when actually in Node.js environment
130
+ if (typeof process === 'undefined' || !process.versions || !process.versions.node) {
131
+ return null;
132
+ }
133
+
134
+ try {
135
+ // Try dynamic import first (works with ES modules)
136
+ const module = await import(path, { assert: { type: 'json' } });
137
+ return module.default || module;
138
+ } catch (error) {
139
+ try {
140
+ // Fallback to fs.readFile for broader compatibility
141
+ const fs = await import('fs/promises');
142
+ const fileContent = await fs.readFile(path, 'utf-8');
143
+ return JSON.parse(fileContent);
144
+ } catch (fsError) {
145
+ return null;
146
+ }
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Load config in browser using dynamic import (for bundlers)
152
+ * @param {string} path - File path
153
+ * @returns {Promise<Object|null>} Configuration object or null
154
+ * @private
155
+ */
156
+ async _loadConfigBrowserImport(path) {
157
+ try {
158
+ // Some bundlers can handle dynamic imports of JSON files
159
+ const module = await import(path);
160
+ return module.default || module;
161
+ } catch (error) {
162
+ return null;
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Normalize config structure to handle different field naming conventions
168
+ * @param {Object} config - Raw configuration object
169
+ * @returns {Object} Normalized configuration
170
+ * @private
171
+ */
172
+ _normalizeConfigStructure(config) {
173
+ const normalized = {...config};
174
+
175
+ // Handle displayOptions -> display mapping
176
+ if (config.displayOptions && !config.display) {
177
+ normalized.display = this._mapDisplayOptions(config.displayOptions);
178
+ delete normalized.displayOptions;
179
+ }
180
+
181
+ // Handle environment-specific configurations
182
+ if (config.environments) {
183
+ // For now, just log that environment configs exist
184
+ // TODO: Implement environment-based config selection
185
+ console.log(`[JSG-LOGGER] Found environment configs for: ${Object.keys(config.environments).join(', ')}`);
186
+ }
187
+
188
+ // Normalize component configurations
189
+ if (config.components) {
190
+ normalized.components = this._normalizeComponents(config.components);
191
+ }
192
+
193
+ return normalized;
194
+ }
195
+
196
+ /**
197
+ * Map displayOptions to display format
198
+ * @param {Object} displayOptions - Original display options
199
+ * @returns {Object} Normalized display configuration
200
+ * @private
201
+ */
202
+ _mapDisplayOptions(displayOptions) {
203
+ return {
204
+ timestamp: displayOptions.showTimestamp ?? true,
205
+ emoji: true, // Always enabled for JSG Logger
206
+ component: displayOptions.showComponent ?? true,
207
+ level: displayOptions.showLevel ?? false,
208
+ message: true, // Always enabled
209
+ jsonPayload: true, // Default enabled
210
+ stackTrace: true, // Default enabled
211
+ environment: displayOptions.showEnvironment ?? false
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Normalize component configurations
217
+ * @param {Object} components - Raw component config
218
+ * @returns {Object} Normalized component config
219
+ * @private
220
+ */
221
+ _normalizeComponents(components) {
222
+ const normalized = {};
223
+
224
+ for (const [name, config] of Object.entries(components)) {
225
+ normalized[name] = {
226
+ emoji: config.emoji || this._getDefaultEmoji(name),
227
+ color: config.color || this._getDefaultColor(name),
228
+ name: config.name || this._formatComponentName(name),
229
+ level: config.level || 'info',
230
+ enabled: config.enabled ?? true,
231
+ description: config.description // Preserve description for documentation
232
+ };
233
+ }
234
+
235
+ return normalized;
236
+ }
237
+
238
+ /**
239
+ * Get default emoji for component
240
+ * @param {string} componentName - Component name
241
+ * @returns {string} Default emoji
242
+ * @private
243
+ */
244
+ _getDefaultEmoji(componentName) {
245
+ const emojiMap = {
246
+ 'astro-build': '🚀',
247
+ 'astro-integration': '⚙️',
248
+ 'content-processing': '📝',
249
+ 'text-utils': '📄',
250
+ 'date-utils': '📅',
251
+ 'react-components': '⚛️',
252
+ 'astro-components': '🌟',
253
+ 'pages': '📄',
254
+ 'config': '⚙️',
255
+ 'seo': '🔍',
256
+ 'performance': '⚡',
257
+ 'dev-server': '🛠️'
258
+ };
259
+
260
+ return emojiMap[componentName] || '🎯';
261
+ }
262
+
263
+ /**
264
+ * Get default color for component
265
+ * @param {string} componentName - Component name
266
+ * @returns {string} Default color
267
+ * @private
268
+ */
269
+ _getDefaultColor(componentName) {
270
+ const colorMap = {
271
+ 'astro-build': '#FF5D01',
272
+ 'astro-integration': '#4A90E2',
273
+ 'content-processing': '#00C896',
274
+ 'text-utils': '#9B59B6',
275
+ 'date-utils': '#3498DB',
276
+ 'react-components': '#61DAFB',
277
+ 'astro-components': '#FF5D01',
278
+ 'pages': '#2ECC71',
279
+ 'config': '#95A5A6',
280
+ 'seo': '#E74C3C',
281
+ 'performance': '#F39C12',
282
+ 'dev-server': '#8E44AD'
283
+ };
284
+
285
+ return colorMap[componentName] || '#4A90E2';
286
+ }
287
+
288
+ /**
289
+ * Format component name for display
290
+ * @param {string} componentName - Raw component name
291
+ * @returns {string} Formatted display name
292
+ * @private
293
+ */
294
+ _formatComponentName(componentName) {
295
+ return componentName
296
+ .split('-')
297
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
298
+ .join('');
299
+ }
300
+
55
301
  /**
56
302
  * Set current file context for override resolution
57
303
  * @param {string} filePath - Current file path being logged from
package/docs/roadmap.md CHANGED
@@ -21,9 +21,9 @@
21
21
 
22
22
  ## 🎯 Current Status
23
23
  **Last Updated:** August 21, 2025
24
- **Current Phase:** Phase 8 Complete - API Enhancement Success
25
- **Status:** **ENHANCED & SIMPLIFIED** - v1.1.0 eliminates project boilerplate with 82% reduction
26
- **Next Session Goal:** Phase 6 - DevTools Panel implementation (optional enhancement)
24
+ **Current Phase:** Phase 9 - Genericize Logger (Remove CACP Hardcoding)
25
+ **Status:** 🚀 **IN PROGRESS** - Making JSG Logger truly generic by removing CACP-specific hardcoded components
26
+ **Current Issue:** Logger still loads CACP defaults instead of project-specific `logger-config.json` files
27
27
 
28
28
  ### Progress Overview
29
29
  - ✅ **COMPLETED:** Multi-environment logger with smart detection
@@ -339,6 +339,19 @@ Console filtering updates
339
339
 
340
340
  ## 📈 Recent Progress
341
341
 
342
+ ### August 21, 2025 - Phase 9 Discovery: CACP Hardcoding Issues 🔍
343
+ - 🐛 **Critical Discovery**: JSG Logger still deeply hardcoded for CACP use cases
344
+ - 🔍 **Issue Identified**: `logger-config.json` files being ignored, falling back to CACP defaults
345
+ - 📋 **Root Causes Documented**: 6 major areas requiring genericization
346
+ 1. `CACPLogger` class name and all references
347
+ 2. Default config with 10 hardcoded CACP components
348
+ 3. Component schemes duplication
349
+ 4. Hardcoded legacy aliases for CACP components
350
+ 5. Core component dependency on 'cacp' logger
351
+ 6. Config loading path resolution issues
352
+ - 🎯 **Phase 9 Planned**: Complete roadmap for making logger truly generic
353
+ - ✅ **Testing Successful**: JSG Logger v1.1.0 API features work, but components wrong
354
+
342
355
  ### August 21, 2025 - Phase 8 API Enhancement Complete ✅
343
356
  - ✅ **JSG Logger v1.1.0** - Major API simplification enhancements shipped
344
357
  - ✅ **Static Singleton Pattern** - `CACPLogger.getInstance()` with auto-initialization
@@ -403,7 +416,7 @@ Console filtering updates
403
416
 
404
417
  ## 🎯 Next Steps
405
418
 
406
- ### **Phase 8: API Enhancement for Project Simplification** 🚀 IN PROGRESS
419
+ ### **Phase 8: API Enhancement for Project Simplification** COMPLETED
407
420
  **Goal**: Eliminate boilerplate code that every project needs to implement when using JSG Logger
408
421
 
409
422
  #### **Background - The Problem**
@@ -553,6 +566,142 @@ export { logger, JSGLogger };
553
566
  5. Test and validate 93% boilerplate reduction
554
567
  6. Document new API in README examples
555
568
 
569
+ #### **🎯 Actual Impact Achieved**
570
+ - ✅ **82% Boilerplate Reduction**: 220 lines → 40 lines in jsg-tech-check-site
571
+ - ✅ **Version Published**: JSG Logger v1.1.0 live on NPM
572
+ - ✅ **All Features Working**: Singleton pattern, auto-discovery, performance logging, non-destructive errors
573
+ - ✅ **Build Integration**: Works in both Astro build-time and client-side contexts
574
+
575
+ ---
576
+
577
+ ### **Phase 9: Genericize Logger (Remove CACP Hardcoding)** 🚀 IN PROGRESS
578
+ **Goal**: Make JSG Logger truly generic by removing all CACP-specific hardcoded components and references
579
+
580
+ #### **Background - The Problem**
581
+ During Phase 8 integration testing with jsg-tech-check-site, we discovered the logger is still deeply hardcoded for CACP (Chrome Audio Control Panel) use cases:
582
+
583
+ **Observable Issues:**
584
+ ```
585
+ [JSG-LOGGER] Component 'astro-build' not found. Available: cacp, soundcloud, youtube, site-detector, websocket, popup, background, priority-manager, settings, test, siteDetector, priorityManager
586
+ ```
587
+
588
+ Despite providing a proper `logger-config.json` with Astro-specific components, the logger falls back to CACP defaults instead of loading the project's configuration.
589
+
590
+ #### **Root Causes - What Makes It CACP-Specific**
591
+
592
+ ##### **1. Class Name & Core References**
593
+ - `CACPLogger` class name should be `JSGLogger` or `GenericLogger`
594
+ - All static method references (`CACPLogger.getInstance()`, etc.)
595
+ - Error messages mentioning "CACP Logger"
596
+ - `window.CACP_Logger` global should be `window.JSG_Logger`
597
+
598
+ ##### **2. Default Configuration Hardcoding**
599
+ **File:** `/config/default-config.json`
600
+ - **Hardcoded project name**: `"CACP Logger"`
601
+ - **10 CACP-specific components**:
602
+ - `cacp` (🎯 CACP-CORE) - should be generic `core`
603
+ - `soundcloud` (🎵 SoundCloud)
604
+ - `youtube` (📹 YouTube)
605
+ - `site-detector` (🔍 SiteDetector)
606
+ - `websocket` (🌐 WebSocket)
607
+ - `popup` (🎛️ Popup)
608
+ - `background` (🔧 Background)
609
+ - `priority-manager` (⚖️ PriorityManager)
610
+ - `settings` (⚙️ Settings)
611
+ - `test` (🧪 Test)
612
+
613
+ ##### **3. Component Schemes Duplication**
614
+ **File:** `/config/component-schemes.js`
615
+ - Duplicates the same 10 hardcoded CACP components
616
+ - Should be empty/minimal by default for generic usage
617
+
618
+ ##### **4. Hardcoded Legacy Aliases**
619
+ ```javascript
620
+ // In createAliases() method:
621
+ this.loggers.siteDetector = this.loggers['site-detector'];
622
+ this.loggers.priorityManager = this.loggers['priority-manager'];
623
+ ```
624
+
625
+ ##### **5. Core Component Dependency**
626
+ ```javascript
627
+ // Initialization requires 'cacp' component:
628
+ if (this.loggers.cacp) {
629
+ this.loggers.cacp.info('CACP Logger initialized', {...});
630
+ }
631
+ ```
632
+
633
+ ##### **6. Config Loading Path Issue**
634
+ - **Critical**: The logger isn't loading our `logger-config.json` properly
635
+ - Falls back to default CACP config instead of using project-specific configurations
636
+ - **Why our Astro config is ignored**: Path resolution or config merging logic issues
637
+
638
+ #### **🔧 Implementation Plan**
639
+
640
+ ##### **Fix 1: Make Default Config Truly Generic**
641
+ **Target**: `/config/default-config.json`
642
+ ```json
643
+ {
644
+ "projectName": "JSG Logger",
645
+ "globalLevel": "info",
646
+ "components": {
647
+ "core": {
648
+ "emoji": "🎯",
649
+ "color": "#4A90E2",
650
+ "name": "Logger-Core",
651
+ "level": "info"
652
+ }
653
+ }
654
+ }
655
+ ```
656
+
657
+ ##### **Fix 2: Rename Core Class**
658
+ **Target**: `/index.js`
659
+ - `CACPLogger` → `JSGLogger`
660
+ - Update all static method references
661
+ - Update error messages
662
+ - Update browser global: `window.CACP_Logger` → `window.JSG_Logger`
663
+
664
+ ##### **Fix 3: Fix Config Loading**
665
+ **Target**: Config manager and initialization
666
+ - Debug why `configPath: 'logger-config.json'` isn't loading properly
667
+ - Ensure project configs override defaults instead of falling back
668
+ - Fix path resolution for various environments (Node.js vs browser)
669
+
670
+ ##### **Fix 4: Remove CACP-Specific Logic**
671
+ **Target**: `/index.js` `createAliases()` method
672
+ - Remove hardcoded legacy aliases for `siteDetector`, `priorityManager`
673
+ - Make aliases configurable if needed, not hardcoded
674
+
675
+ ##### **Fix 5: Use Configurable Core Component**
676
+ **Target**: Initialization logging
677
+ - Replace `this.loggers.cacp.info()` with configurable core component
678
+ - Use `this.loggers.core` or first available component
679
+ - Graceful fallback if no components configured
680
+
681
+ ##### **Fix 6: Clean Component Schemes**
682
+ **Target**: `/config/component-schemes.js`
683
+ - Remove all CACP-specific hardcoded components
684
+ - Keep only minimal example or make it empty
685
+ - Let projects define their own components
686
+
687
+ #### **🎯 Success Criteria**
688
+ 1. ✅ **Generic by Default**: Fresh installations work without CACP references
689
+ 2. ✅ **Config Loading Works**: Project-specific `logger-config.json` files are properly loaded
690
+ 3. ✅ **No CACP Dependencies**: Logger works without any CACP-specific components
691
+ 4. ✅ **Clean API**: `JSGLogger.getInstance()` instead of `CACPLogger.getInstance()`
692
+ 5. ✅ **Test with Real Project**: jsg-tech-check-site loads Astro components correctly
693
+
694
+ #### **📋 Implementation Steps**
695
+ 1. **Debug config loading** - Fix why `logger-config.json` is ignored
696
+ 2. **Rename core class** - `CACPLogger` → `JSGLogger`
697
+ 3. **Replace default config** - Remove 10 CACP components, use minimal generic
698
+ 4. **Remove hardcoded aliases** - Make legacy aliases configurable
699
+ 5. **Fix core component** - Use configurable core for init logging
700
+ 6. **Update browser global** - `window.JSG_Logger`
701
+ 7. **Test with jsg-tech-check-site** - Verify Astro components load correctly
702
+ 8. **Version bump** - Patch or minor version for breaking changes
703
+ 9. **Publish updated package** - Deploy generic version to NPM
704
+
556
705
  ---
557
706
 
558
707
  ### **Previous Optional Enhancements** (Lower Priority)
@@ -4,6 +4,8 @@
4
4
  */
5
5
 
6
6
  import { COMPONENT_SCHEME, LEVEL_SCHEME } from '../config/component-schemes.js';
7
+ import pinoColada from 'pino-colada';
8
+ // Note: pino-pretty imported conditionally to avoid browser bundle issues
7
9
 
8
10
  /**
9
11
  * Create CLI formatter using pino-colada or pino-pretty
@@ -11,33 +13,44 @@ import { COMPONENT_SCHEME, LEVEL_SCHEME } from '../config/component-schemes.js';
11
13
  */
12
14
  export const createCLIFormatter = () => {
13
15
  try {
14
- // Try to use pino-colada if available
15
- const pinoColada = require('pino-colada');
16
- return pinoColada();
16
+ // Try pino-colada first (best formatting)
17
+ const colada = pinoColada();
18
+ colada.pipe(process.stdout);
19
+ return colada;
17
20
  } catch (error) {
18
- // Fallback to pino-pretty if pino-colada not available
19
- try {
20
- const pinoPretty = require('pino-pretty');
21
- return pinoPretty({
22
- colorize: true,
23
- translateTime: 'HH:MM:ss.l',
24
- messageFormat: (log, messageKey, levelLabel) => {
21
+ // Ultimate fallback - basic formatted output (works in all environments)
22
+ return {
23
+ write: (chunk) => {
24
+ try {
25
+ const log = JSON.parse(chunk);
26
+
27
+ // Get component info
25
28
  const component = COMPONENT_SCHEME[log.name] || COMPONENT_SCHEME['cacp'];
26
29
  const componentName = component.name.toUpperCase().replace(/([a-z])([A-Z])/g, '$1-$2');
30
+
31
+ // Get level info
27
32
  const level = LEVEL_SCHEME[log.level] || LEVEL_SCHEME[30];
28
- return `${level.emoji} [${componentName}] ${log[messageKey]}`;
29
- },
30
- customPrettifiers: {
31
- level: () => '', // Hide level since we show it in messageFormat
32
- time: (timestamp) => timestamp,
33
- name: () => '' // Hide name since we show it in messageFormat
33
+
34
+ // Format timestamp like pino-pretty
35
+ const timestamp = new Date(log.time).toLocaleTimeString('en-US', {
36
+ hour12: false,
37
+ hour: '2-digit',
38
+ minute: '2-digit',
39
+ second: '2-digit',
40
+ fractionalSecondDigits: 1
41
+ });
42
+
43
+ // Format message like pino-pretty messageFormat
44
+ const message = `${level.emoji} [${componentName}] ${log.msg || ''}`;
45
+
46
+ // Output with timestamp prefix
47
+ console.log(`${timestamp} ${message}`);
48
+
49
+ } catch (error) {
50
+ // Raw fallback
51
+ console.log(chunk);
34
52
  }
35
- });
36
- } catch (error) {
37
- // Ultimate fallback - basic JSON
38
- return {
39
- write: (data) => console.log(data)
40
- };
41
- }
53
+ }
54
+ };
42
55
  }
43
56
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crimsonsunset/jsg-logger",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "type": "module",
5
5
  "description": "JSG Logger - Multi-environment logger with smart detection, file-level overrides, and beautiful console formatting",
6
6
  "main": "index.js",