spec-up-t 1.5.0 → 1.6.0-beta.1

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.
@@ -15,30 +15,127 @@ class Logger {
15
15
  static success(message, ...args) {
16
16
  console.log(chalk.green('✅'), chalk.green(message), ...args);
17
17
  messageCollector.addMessage('success', message, args);
18
+
19
+ console.log(); // Extra newline for readability
18
20
  }
19
21
 
20
22
  /**
21
23
  * Error messages - red with X mark
24
+ *
25
+ * Enhanced error logging with optional context and actionable guidance.
26
+ *
27
+ * @param {string} message - The main error message
28
+ * @param {...any} args - Additional arguments. Can include:
29
+ * - Regular values (strings, numbers, objects) for message formatting
30
+ * - An options object (if last arg is object with 'hint', 'context', or 'details' keys):
31
+ * - hint: Actionable suggestion for fixing the error
32
+ * - context: Additional context about where/why the error occurred
33
+ * - details: Technical details or error objects
34
+ *
35
+ * @example
36
+ * Logger.error('File not found', {
37
+ * context: 'specs.json',
38
+ * hint: 'Create a specs.json file in your project root',
39
+ * details: error.message
40
+ * });
22
41
  */
23
42
  static error(message, ...args) {
24
- console.log(chalk.red('❌'), chalk.red(message), ...args);
25
- messageCollector.addMessage('error', message, args);
43
+ // Extract options object if present (last arg with special keys)
44
+ const lastArg = args[args.length - 1];
45
+ const isOptionsObject = lastArg && typeof lastArg === 'object' &&
46
+ (lastArg.hint || lastArg.context || lastArg.details);
47
+
48
+ const options = isOptionsObject ? args.pop() : {};
49
+ const regularArgs = args;
50
+
51
+ // Display main error message
52
+ console.log(chalk.red('❌'), chalk.red(message), ...regularArgs);
53
+
54
+ // Display context if provided - helps identify the scope of the error
55
+ if (options.context) {
56
+ console.log(chalk.red(' Context:'), chalk.gray(options.context));
57
+ }
58
+
59
+ // Display technical details if provided - useful for debugging
60
+ if (options.details) {
61
+ const detailsStr = typeof options.details === 'object'
62
+ ? JSON.stringify(options.details, null, 2)
63
+ : String(options.details);
64
+ console.log(chalk.red(' Details:'), chalk.gray(detailsStr));
65
+ }
66
+
67
+ // Display actionable hint if provided - most valuable for authors
68
+ if (options.hint) {
69
+ console.log(chalk.yellow(' 💡 How to fix:'), chalk.yellow(options.hint));
70
+ }
71
+
72
+ // Collect message with all context for healthcheck/JSON output
73
+ messageCollector.addMessage('error', message, [...regularArgs, options]);
74
+
75
+ console.log(); // Extra newline for readability
26
76
  }
27
77
 
28
78
  /**
29
79
  * Warning messages - yellow with warning symbol
80
+ *
81
+ * Enhanced warning logging with optional context and actionable guidance.
82
+ *
83
+ * @param {string} message - The main warning message
84
+ * @param {...any} args - Additional arguments. Can include:
85
+ * - Regular values (strings, numbers, objects) for message formatting
86
+ * - An options object (if last arg is object with 'hint', 'context', or 'details' keys):
87
+ * - hint: Actionable suggestion for addressing the warning
88
+ * - context: Additional context about where/why the warning occurred
89
+ * - details: Technical details or related information
90
+ *
91
+ * @example
92
+ * Logger.warn('Using fallback configuration', {
93
+ * context: 'specs.json missing optional field',
94
+ * hint: 'Add "output_path" to specs.json for better control',
95
+ * details: 'Using default: ./docs'
96
+ * });
30
97
  */
31
98
  static warn(message, ...args) {
32
- console.log(chalk.yellow('🟡'), chalk.yellow(message), ...args);
33
- messageCollector.addMessage('warn', message, args);
99
+ // Extract options object if present (last arg with special keys)
100
+ const lastArg = args[args.length - 1];
101
+ const isOptionsObject = lastArg && typeof lastArg === 'object' &&
102
+ (lastArg.hint || lastArg.context || lastArg.details);
103
+
104
+ const options = isOptionsObject ? args.pop() : {};
105
+ const regularArgs = args;
106
+
107
+ // Display main warning message
108
+ console.log(chalk.keyword('orange')('❗'), chalk.yellow(message), ...regularArgs);
109
+
110
+ // Display context if provided - helps identify the scope of the warning
111
+ if (options.context) {
112
+ console.log(chalk.yellow(' Context:'), chalk.gray(options.context));
113
+ }
114
+
115
+ // Display technical details if provided - useful for understanding the situation
116
+ if (options.details) {
117
+ const detailsStr = typeof options.details === 'object'
118
+ ? JSON.stringify(options.details, null, 2)
119
+ : String(options.details);
120
+ console.log(chalk.yellow(' Details:'), chalk.gray(detailsStr));
121
+ }
122
+
123
+ // Display actionable hint if provided - helps authors improve their spec
124
+ if (options.hint) {
125
+ console.log(chalk.cyan(' 💡 Suggestion:'), chalk.cyan(options.hint));
126
+ }
127
+
128
+ // Collect message with all context for healthcheck/JSON output
129
+ messageCollector.addMessage('warn', message, [...regularArgs, options]);
130
+
131
+ console.log(); // Extra newline for readability
34
132
  }
35
133
 
36
- /**
37
- * Info messages - blue with info symbol
38
- */
39
134
  static info(message, ...args) {
40
- console.log(chalk.blue('📒'), chalk.blue(message), ...args);
135
+ console.log(chalk.blue('📋'), chalk.blue(message), ...args);
41
136
  messageCollector.addMessage('info', message, args);
137
+
138
+ console.log(); // Extra newline for readability
42
139
  }
43
140
 
44
141
  /**
@@ -47,6 +144,8 @@ class Logger {
47
144
  static process(message, ...args) {
48
145
  console.log(chalk.cyan('🔄'), chalk.cyan(message), ...args);
49
146
  messageCollector.addMessage('process', message, args);
147
+
148
+ console.log(); // Extra newline for readability
50
149
  }
51
150
 
52
151
  /**
@@ -55,14 +154,18 @@ class Logger {
55
154
  static debug(message, ...args) {
56
155
  console.log(chalk.gray('🔍'), chalk.gray(message), ...args);
57
156
  messageCollector.addMessage('debug', message, args);
157
+
158
+ console.log(); // Extra newline for readability
58
159
  }
59
160
 
60
161
  /**
61
162
  * Highlight important data - magenta
62
163
  */
63
164
  static highlight(message, ...args) {
64
- console.log(chalk.blue(''), chalk.blue(message), ...args);
165
+ console.log(chalk.blue('📋'), chalk.blue(message), ...args);
65
166
  messageCollector.addMessage('highlight', message, args);
167
+
168
+ console.log(); // Extra newline for readability
66
169
  }
67
170
 
68
171
  /**
@@ -71,6 +174,8 @@ class Logger {
71
174
  static separator() {
72
175
  console.log(chalk.gray('═'.repeat(60)));
73
176
  messageCollector.addMessage('separator', '═'.repeat(60), []);
177
+
178
+ console.log(); // Extra newline for readability
74
179
  }
75
180
 
76
181
  /**
@@ -82,6 +187,8 @@ class Logger {
82
187
  const progressMessage = `[${bar}] ${percentage}% ${message}`;
83
188
  console.log(chalk.cyan(`📊 ${progressMessage}`));
84
189
  messageCollector.addMessage('progress', progressMessage, [current, total]);
190
+
191
+ console.log(); // Extra newline for readability
85
192
  }
86
193
  }
87
194
 
@@ -109,12 +109,14 @@ const templateTags = {
109
109
  * Pattern breakdown:
110
110
  * - ^def$ → Exact match for "def" (definition)
111
111
  * - ^ref$ → Exact match for "ref" (reference)
112
+ * - ^iref$ → Exact match for "iref" (inline reference - copies existing term)
112
113
  * - ^xref → Starts with "xref" (external reference)
113
114
  * - ^tref → Starts with "tref" (typed reference)
114
115
  *
115
116
  * Examples:
116
117
  * - "def" → matches
117
118
  * - "ref" → matches
119
+ * - "iref" → matches
118
120
  * - "xref" → matches
119
121
  * - "tref" → matches
120
122
  * - "xref:spec,term" → matches (starts with xref)
@@ -122,7 +124,7 @@ const templateTags = {
122
124
  * Flags:
123
125
  * - i: case-insensitive matching
124
126
  */
125
- terminology: /^def$|^ref$|^xref|^tref$/i
127
+ terminology: /^def$|^ref$|^iref$|^xref|^tref$/i
126
128
  };
127
129
 
128
130
  /**
@@ -0,0 +1,290 @@
1
+ /**
2
+ * @file Unit tests for enhanced Logger.error and Logger.warn functionality
3
+ *
4
+ * Tests the new context, hint, and details options for error and warning messages.
5
+ * Ensures backward compatibility with existing simple message calls.
6
+ */
7
+
8
+ const Logger = require('../src/utils/logger');
9
+ const messageCollector = require('../src/utils/message-collector');
10
+
11
+ // Mock console.log to capture output
12
+ let consoleLogs = [];
13
+ const originalConsoleLog = console.log;
14
+
15
+ beforeAll(() => {
16
+ console.log = (...args) => {
17
+ consoleLogs.push(args);
18
+ };
19
+ });
20
+
21
+ afterAll(() => {
22
+ console.log = originalConsoleLog;
23
+ });
24
+
25
+ beforeEach(() => {
26
+ consoleLogs = [];
27
+ messageCollector.clearMessages();
28
+ });
29
+
30
+ afterEach(() => {
31
+ messageCollector.stopCollecting();
32
+ });
33
+
34
+ describe('Enhanced Logger.error', () => {
35
+ test('should work with simple message (backward compatibility)', () => {
36
+ Logger.error('Simple error message');
37
+
38
+ expect(consoleLogs.length).toBeGreaterThan(0);
39
+ expect(consoleLogs[0].join(' ')).toContain('Simple error message');
40
+ });
41
+
42
+ test('should work with message and regular arguments', () => {
43
+ Logger.error('Error with args', 'arg1', 123);
44
+
45
+ expect(consoleLogs.length).toBeGreaterThan(0);
46
+ const output = consoleLogs[0].join(' ');
47
+ expect(output).toContain('Error with args');
48
+ expect(output).toContain('arg1');
49
+ expect(output).toContain('123');
50
+ });
51
+
52
+ test('should display context when provided', () => {
53
+ consoleLogs = [];
54
+ Logger.error('Test error', { context: 'specs.json' });
55
+
56
+ const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
57
+ expect(allOutput).toContain('Context:');
58
+ expect(allOutput).toContain('specs.json');
59
+ });
60
+
61
+ test('should display hint when provided', () => {
62
+ consoleLogs = [];
63
+ Logger.error('Test error', { hint: 'Run npm install' });
64
+
65
+ const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
66
+ expect(allOutput).toContain('How to fix:');
67
+ expect(allOutput).toContain('Run npm install');
68
+ });
69
+
70
+ test('should display details when provided', () => {
71
+ consoleLogs = [];
72
+ Logger.error('Test error', { details: 'Error code: 404' });
73
+
74
+ const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
75
+ expect(allOutput).toContain('Details:');
76
+ expect(allOutput).toContain('Error code: 404');
77
+ });
78
+
79
+ test('should handle all options together', () => {
80
+ consoleLogs = [];
81
+ Logger.error('Test error', {
82
+ context: 'Test context',
83
+ hint: 'Test hint',
84
+ details: 'Test details'
85
+ });
86
+
87
+ const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
88
+ expect(allOutput).toContain('Test error');
89
+ expect(allOutput).toContain('Context:');
90
+ expect(allOutput).toContain('Test context');
91
+ expect(allOutput).toContain('How to fix:');
92
+ expect(allOutput).toContain('Test hint');
93
+ expect(allOutput).toContain('Details:');
94
+ expect(allOutput).toContain('Test details');
95
+ });
96
+
97
+ test('should handle regular args mixed with options', () => {
98
+ consoleLogs = [];
99
+ Logger.error('Error', 'arg1', 'arg2', { hint: 'Fix it' });
100
+
101
+ const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
102
+ expect(allOutput).toContain('Error');
103
+ expect(allOutput).toContain('arg1');
104
+ expect(allOutput).toContain('arg2');
105
+ expect(allOutput).toContain('Fix it');
106
+ });
107
+
108
+ test('should handle object details', () => {
109
+ consoleLogs = [];
110
+ Logger.error('Test error', {
111
+ details: { code: 404, message: 'Not found' }
112
+ });
113
+
114
+ const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
115
+ expect(allOutput).toContain('404');
116
+ expect(allOutput).toContain('Not found');
117
+ });
118
+
119
+ test('should not treat regular object as options', () => {
120
+ consoleLogs = [];
121
+ Logger.error('Test error', { someData: 'value' });
122
+
123
+ const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
124
+ expect(allOutput).toContain('Test error');
125
+ // Should treat it as regular argument, not as options
126
+ // The object will be stringified as [object Object]
127
+ expect(allOutput).toContain('[object Object]');
128
+ });
129
+
130
+ test('should collect enhanced messages', () => {
131
+ messageCollector.startCollecting('test');
132
+
133
+ Logger.error('Test error', {
134
+ context: 'test context',
135
+ hint: 'test hint',
136
+ details: 'test details'
137
+ });
138
+
139
+ const messages = messageCollector.getMessages();
140
+ expect(messages).toHaveLength(1);
141
+ expect(messages[0].type).toBe('error');
142
+ expect(messages[0].message).toBe('Test error');
143
+
144
+ // Should have collected the options
145
+ const additionalData = messages[0].additionalData;
146
+ expect(additionalData).toBeDefined();
147
+ expect(additionalData.length).toBeGreaterThan(0);
148
+
149
+ messageCollector.stopCollecting();
150
+ });
151
+ });
152
+
153
+ describe('Enhanced Logger.warn', () => {
154
+ test('should work with simple message (backward compatibility)', () => {
155
+ Logger.warn('Simple warning message');
156
+
157
+ expect(consoleLogs.length).toBeGreaterThan(0);
158
+ expect(consoleLogs[0].join(' ')).toContain('Simple warning message');
159
+ });
160
+
161
+ test('should work with message and regular arguments', () => {
162
+ Logger.warn('Warning with args', 'arg1', 123);
163
+
164
+ expect(consoleLogs.length).toBeGreaterThan(0);
165
+ const output = consoleLogs[0].join(' ');
166
+ expect(output).toContain('Warning with args');
167
+ expect(output).toContain('arg1');
168
+ expect(output).toContain('123');
169
+ });
170
+
171
+ test('should display context when provided', () => {
172
+ consoleLogs = [];
173
+ Logger.warn('Test warning', { context: 'Configuration file' });
174
+
175
+ const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
176
+ expect(allOutput).toContain('Context:');
177
+ expect(allOutput).toContain('Configuration file');
178
+ });
179
+
180
+ test('should display hint when provided', () => {
181
+ consoleLogs = [];
182
+ Logger.warn('Test warning', { hint: 'Consider updating to latest version' });
183
+
184
+ const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
185
+ expect(allOutput).toContain('Suggestion:');
186
+ expect(allOutput).toContain('Consider updating to latest version');
187
+ });
188
+
189
+ test('should display details when provided', () => {
190
+ consoleLogs = [];
191
+ Logger.warn('Test warning', { details: 'Using fallback: default' });
192
+
193
+ const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
194
+ expect(allOutput).toContain('Details:');
195
+ expect(allOutput).toContain('Using fallback: default');
196
+ });
197
+
198
+ test('should handle all options together', () => {
199
+ consoleLogs = [];
200
+ Logger.warn('Test warning', {
201
+ context: 'Test context',
202
+ hint: 'Test suggestion',
203
+ details: 'Test details'
204
+ });
205
+
206
+ const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
207
+ expect(allOutput).toContain('Test warning');
208
+ expect(allOutput).toContain('Context:');
209
+ expect(allOutput).toContain('Test context');
210
+ expect(allOutput).toContain('Suggestion:');
211
+ expect(allOutput).toContain('Test suggestion');
212
+ expect(allOutput).toContain('Details:');
213
+ expect(allOutput).toContain('Test details');
214
+ });
215
+
216
+ test('should handle regular args mixed with options', () => {
217
+ consoleLogs = [];
218
+ Logger.warn('Warning', 'arg1', 'arg2', { hint: 'Update config' });
219
+
220
+ const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
221
+ expect(allOutput).toContain('Warning');
222
+ expect(allOutput).toContain('arg1');
223
+ expect(allOutput).toContain('arg2');
224
+ expect(allOutput).toContain('Update config');
225
+ });
226
+
227
+ test('should collect enhanced messages', () => {
228
+ messageCollector.startCollecting('test');
229
+
230
+ Logger.warn('Test warning', {
231
+ context: 'test context',
232
+ hint: 'test hint',
233
+ details: 'test details'
234
+ });
235
+
236
+ const messages = messageCollector.getMessages();
237
+ expect(messages).toHaveLength(1);
238
+ expect(messages[0].type).toBe('warn');
239
+ expect(messages[0].message).toBe('Test warning');
240
+
241
+ messageCollector.stopCollecting();
242
+ });
243
+ });
244
+
245
+ describe('Edge Cases', () => {
246
+ test('should handle empty options object', () => {
247
+ consoleLogs = [];
248
+ Logger.error('Test', {});
249
+
250
+ const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
251
+ expect(allOutput).toContain('Test');
252
+ // Should just show the message without extra lines
253
+ });
254
+
255
+ test('should handle null and undefined in options', () => {
256
+ consoleLogs = [];
257
+ Logger.error('Test', {
258
+ context: null,
259
+ hint: undefined,
260
+ details: 'valid'
261
+ });
262
+
263
+ const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
264
+ expect(allOutput).toContain('Test');
265
+ expect(allOutput).toContain('Details:');
266
+ expect(allOutput).toContain('valid');
267
+ });
268
+
269
+ test('should handle special characters in options', () => {
270
+ consoleLogs = [];
271
+ Logger.error('Test', {
272
+ hint: 'Fix with: npm run build && npm test'
273
+ });
274
+
275
+ const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
276
+ expect(allOutput).toContain('npm run build && npm test');
277
+ });
278
+
279
+ test('should handle multiline strings in options', () => {
280
+ consoleLogs = [];
281
+ Logger.error('Test', {
282
+ details: 'Line 1\nLine 2\nLine 3'
283
+ });
284
+
285
+ const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
286
+ expect(allOutput).toContain('Line 1');
287
+ expect(allOutput).toContain('Line 2');
288
+ expect(allOutput).toContain('Line 3');
289
+ });
290
+ });
@@ -1 +0,0 @@
1
- /* Currently empty */
@@ -1,81 +0,0 @@
1
- # Logger Utility
2
-
3
- A centralized logging utility for the spec-up-t project that provides consistent, color-coded console output using chalk.
4
-
5
- ## Features
6
-
7
- - **Color-coded messages** for different log levels
8
- - **Consistent icons** and formatting
9
- - **Progress indicators** with visual progress bars
10
- - **Section separators** for organized output
11
- - **Terminal-friendly** symbols that work across platforms
12
-
13
- ## Usage
14
-
15
- ```javascript
16
- const Logger = require('./src/utils/logger');
17
-
18
- // Success messages (green with checkmark)
19
- Logger.success('Operation completed successfully');
20
-
21
- // Error messages (red with X mark)
22
- Logger.error('Failed to process request');
23
-
24
- // Warning messages (yellow with warning symbol)
25
- Logger.warn('Configuration file not found, using defaults');
26
-
27
- // Info messages (blue with info symbol)
28
- Logger.info('Processing 42 external references');
29
-
30
- // Processing status (cyan with arrow)
31
- Logger.process('Processing repository: owner/repo (15 terms)');
32
-
33
- // Highlighted information (magenta with star)
34
- Logger.highlight('Grouped 42 terms into 6 repositories');
35
-
36
- // Progress indicators
37
- Logger.progress(3, 5, 'Processing terms'); // Shows: [████████████░░░░░░░░] 60% Processing terms
38
-
39
- // Section separators
40
- Logger.separator(); // Shows: ════════════════════════════════════════════════════════════
41
- ```
42
-
43
- ## Log Levels
44
-
45
- | Method | Color | Icon | Purpose |
46
- |--------|-------|------|---------|
47
- | `success()` | Green | ✓ | Successful operations |
48
- | `error()` | Red | ✗ | Errors and failures |
49
- | `warn()` | Yellow | ⚠ | Warnings and non-critical issues |
50
- | `info()` | Blue | ℹ | General information |
51
- | `process()` | Cyan | → | Processing status updates |
52
- | `highlight()` | Magenta | ★ | Important data/summaries |
53
- | `debug()` | Gray | ◦ | Debug information |
54
-
55
- ## Migration from console.log
56
-
57
- **Before:**
58
- ```javascript
59
- console.log(`✅ Successfully processed ${count} items`);
60
- console.log(`❌ Failed to fetch data from ${url}`);
61
- console.log(`⚠️ Missing configuration file`);
62
- ```
63
-
64
- **After:**
65
- ```javascript
66
- Logger.success(`Successfully processed ${count} items`);
67
- Logger.error(`Failed to fetch data from ${url}`);
68
- Logger.warn('Missing configuration file');
69
- ```
70
-
71
- ## Dependencies
72
-
73
- - `chalk@4` - For terminal colors (CommonJS compatible version)
74
-
75
- ## Installation
76
-
77
- The logger is automatically available when chalk v4 is installed:
78
-
79
- ```bash
80
- npm install chalk@4
81
- ```