spec-up-t 1.4.1 → 1.5.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/assets/compiled/body.js +2 -2
- package/assets/compiled/head.css +2 -2
- package/assets/css/embedded-libraries/bootstrap.min.css +1 -1
- package/assets/css/header-navbar.css +4 -4
- package/assets/js/github-issues.js +3 -3
- package/examples/read-console-messages.js +102 -0
- package/gulpfile.js +42 -1
- package/index.js +49 -1
- package/package.json +2 -1
- package/src/health-check.js +47 -629
- package/src/install-from-boilerplate/config-scripts-keys.js +1 -1
- package/src/markdown-it/README.md +2 -14
- package/src/markdown-it/index.js +1 -7
- package/src/parsers/template-tag-parser.js +19 -3
- package/src/pipeline/postprocessing/definition-list-postprocessor.js +4 -2
- package/src/pipeline/references/collect-external-references.js +65 -8
- package/src/pipeline/references/external-references-service.js +55 -10
- package/src/pipeline/references/fetch-terms-from-index.js +62 -1
- package/src/pipeline/references/process-xtrefs-data.js +34 -0
- package/src/pipeline/references/xtref-utils.js +22 -3
- package/src/pipeline/rendering/render-spec-document.js +0 -1
- package/src/run-healthcheck.js +177 -0
- package/src/utils/logger.js +15 -1
- package/src/utils/message-collector.js +144 -0
- package/templates/template.html +6 -6
- package/test/message-collector.test.js +286 -0
- package/src/markdown-it/link-enhancement.js +0 -98
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Unit tests for message-collector utility
|
|
3
|
+
*
|
|
4
|
+
* Tests the message collection functionality that captures console output
|
|
5
|
+
* from Logger calls and stores them in JSON format.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const messageCollector = require('../src/utils/message-collector');
|
|
9
|
+
const Logger = require('../src/utils/logger');
|
|
10
|
+
const fs = require('fs-extra');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
describe('Message Collector', () => {
|
|
14
|
+
// Clean up before and after each test
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
messageCollector.clearMessages();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
messageCollector.stopCollecting();
|
|
21
|
+
messageCollector.clearMessages();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('Basic Collection', () => {
|
|
25
|
+
test('should start and stop collecting', () => {
|
|
26
|
+
expect(messageCollector.getStatistics().isCollecting).toBe(false);
|
|
27
|
+
|
|
28
|
+
messageCollector.startCollecting('test');
|
|
29
|
+
expect(messageCollector.getStatistics().isCollecting).toBe(true);
|
|
30
|
+
expect(messageCollector.getStatistics().currentOperation).toBe('test');
|
|
31
|
+
|
|
32
|
+
messageCollector.stopCollecting();
|
|
33
|
+
expect(messageCollector.getStatistics().isCollecting).toBe(false);
|
|
34
|
+
expect(messageCollector.getStatistics().currentOperation).toBe(null);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('should collect messages when active', () => {
|
|
38
|
+
messageCollector.startCollecting('test');
|
|
39
|
+
|
|
40
|
+
Logger.info('Test message');
|
|
41
|
+
Logger.success('Success message');
|
|
42
|
+
Logger.warn('Warning message');
|
|
43
|
+
|
|
44
|
+
const messages = messageCollector.getMessages();
|
|
45
|
+
expect(messages).toHaveLength(3);
|
|
46
|
+
expect(messages[0].message).toBe('Test message');
|
|
47
|
+
expect(messages[0].type).toBe('info');
|
|
48
|
+
expect(messages[0].operation).toBe('test');
|
|
49
|
+
|
|
50
|
+
messageCollector.stopCollecting();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('should not collect messages when inactive', () => {
|
|
54
|
+
Logger.info('This should not be collected');
|
|
55
|
+
|
|
56
|
+
const messages = messageCollector.getMessages();
|
|
57
|
+
expect(messages).toHaveLength(0);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('should clear messages', () => {
|
|
61
|
+
messageCollector.startCollecting('test');
|
|
62
|
+
Logger.info('Message 1');
|
|
63
|
+
Logger.info('Message 2');
|
|
64
|
+
|
|
65
|
+
expect(messageCollector.getMessages()).toHaveLength(2);
|
|
66
|
+
|
|
67
|
+
messageCollector.clearMessages();
|
|
68
|
+
expect(messageCollector.getMessages()).toHaveLength(0);
|
|
69
|
+
|
|
70
|
+
messageCollector.stopCollecting();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('Message Structure', () => {
|
|
75
|
+
test('should include all required fields', () => {
|
|
76
|
+
messageCollector.startCollecting('test');
|
|
77
|
+
Logger.info('Test message', 'extra', 'data');
|
|
78
|
+
|
|
79
|
+
const messages = messageCollector.getMessages();
|
|
80
|
+
expect(messages[0]).toHaveProperty('timestamp');
|
|
81
|
+
expect(messages[0]).toHaveProperty('type');
|
|
82
|
+
expect(messages[0]).toHaveProperty('message');
|
|
83
|
+
expect(messages[0]).toHaveProperty('operation');
|
|
84
|
+
|
|
85
|
+
messageCollector.stopCollecting();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('should capture additional arguments', () => {
|
|
89
|
+
messageCollector.startCollecting('test');
|
|
90
|
+
Logger.info('Test', 'arg1', 'arg2', 123);
|
|
91
|
+
|
|
92
|
+
const messages = messageCollector.getMessages();
|
|
93
|
+
expect(messages[0].additionalData).toEqual(['arg1', 'arg2', '123']);
|
|
94
|
+
|
|
95
|
+
messageCollector.stopCollecting();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('should handle different message types', () => {
|
|
99
|
+
messageCollector.startCollecting('test');
|
|
100
|
+
|
|
101
|
+
Logger.success('Success');
|
|
102
|
+
Logger.error('Error');
|
|
103
|
+
Logger.warn('Warning');
|
|
104
|
+
Logger.info('Info');
|
|
105
|
+
Logger.process('Process');
|
|
106
|
+
Logger.highlight('Highlight');
|
|
107
|
+
Logger.debug('Debug');
|
|
108
|
+
Logger.separator();
|
|
109
|
+
|
|
110
|
+
const messages = messageCollector.getMessages();
|
|
111
|
+
expect(messages).toHaveLength(8);
|
|
112
|
+
|
|
113
|
+
const types = messages.map(m => m.type);
|
|
114
|
+
expect(types).toContain('success');
|
|
115
|
+
expect(types).toContain('error');
|
|
116
|
+
expect(types).toContain('warn');
|
|
117
|
+
expect(types).toContain('info');
|
|
118
|
+
expect(types).toContain('process');
|
|
119
|
+
expect(types).toContain('highlight');
|
|
120
|
+
expect(types).toContain('debug');
|
|
121
|
+
expect(types).toContain('separator');
|
|
122
|
+
|
|
123
|
+
messageCollector.stopCollecting();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('Statistics', () => {
|
|
128
|
+
test('should calculate message counts by type', () => {
|
|
129
|
+
messageCollector.startCollecting('test');
|
|
130
|
+
|
|
131
|
+
Logger.info('Info 1');
|
|
132
|
+
Logger.info('Info 2');
|
|
133
|
+
Logger.success('Success 1');
|
|
134
|
+
Logger.error('Error 1');
|
|
135
|
+
|
|
136
|
+
const stats = messageCollector.getStatistics();
|
|
137
|
+
expect(stats.total).toBe(4);
|
|
138
|
+
expect(stats.byType.info).toBe(2);
|
|
139
|
+
expect(stats.byType.success).toBe(1);
|
|
140
|
+
expect(stats.byType.error).toBe(1);
|
|
141
|
+
|
|
142
|
+
messageCollector.stopCollecting();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('should track messages by operation', () => {
|
|
146
|
+
messageCollector.startCollecting('render');
|
|
147
|
+
Logger.info('Render 1');
|
|
148
|
+
Logger.info('Render 2');
|
|
149
|
+
messageCollector.stopCollecting();
|
|
150
|
+
|
|
151
|
+
messageCollector.startCollecting('collect');
|
|
152
|
+
Logger.info('Collect 1');
|
|
153
|
+
messageCollector.stopCollecting();
|
|
154
|
+
|
|
155
|
+
const stats = messageCollector.getStatistics();
|
|
156
|
+
expect(stats.byOperation.render).toBe(2);
|
|
157
|
+
expect(stats.byOperation.collect).toBe(1);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('File Operations', () => {
|
|
162
|
+
const testOutputPath = path.join(__dirname, '.test-cache', 'test-messages.json');
|
|
163
|
+
|
|
164
|
+
beforeEach(async () => {
|
|
165
|
+
// Clean up test output
|
|
166
|
+
await fs.remove(path.dirname(testOutputPath));
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
afterEach(async () => {
|
|
170
|
+
// Clean up test output
|
|
171
|
+
await fs.remove(path.dirname(testOutputPath));
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('should save messages to file', async () => {
|
|
175
|
+
messageCollector.startCollecting('test');
|
|
176
|
+
Logger.info('Test message');
|
|
177
|
+
Logger.success('Success');
|
|
178
|
+
messageCollector.stopCollecting();
|
|
179
|
+
|
|
180
|
+
const savedPath = await messageCollector.saveMessages(testOutputPath);
|
|
181
|
+
expect(savedPath).toBe(testOutputPath);
|
|
182
|
+
|
|
183
|
+
const exists = await fs.pathExists(testOutputPath);
|
|
184
|
+
expect(exists).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('should create directory if it does not exist', async () => {
|
|
188
|
+
messageCollector.startCollecting('test');
|
|
189
|
+
Logger.info('Test');
|
|
190
|
+
messageCollector.stopCollecting();
|
|
191
|
+
|
|
192
|
+
await messageCollector.saveMessages(testOutputPath);
|
|
193
|
+
|
|
194
|
+
const dirExists = await fs.pathExists(path.dirname(testOutputPath));
|
|
195
|
+
expect(dirExists).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('should include metadata in saved file', async () => {
|
|
199
|
+
messageCollector.startCollecting('test');
|
|
200
|
+
Logger.info('Info message');
|
|
201
|
+
Logger.success('Success message');
|
|
202
|
+
Logger.warn('Warning message');
|
|
203
|
+
messageCollector.stopCollecting();
|
|
204
|
+
|
|
205
|
+
await messageCollector.saveMessages(testOutputPath);
|
|
206
|
+
|
|
207
|
+
const output = await fs.readJson(testOutputPath);
|
|
208
|
+
|
|
209
|
+
expect(output).toHaveProperty('metadata');
|
|
210
|
+
expect(output).toHaveProperty('messages');
|
|
211
|
+
|
|
212
|
+
expect(output.metadata).toHaveProperty('generatedAt');
|
|
213
|
+
expect(output.metadata).toHaveProperty('totalMessages');
|
|
214
|
+
expect(output.metadata).toHaveProperty('operations');
|
|
215
|
+
expect(output.metadata).toHaveProperty('messagesByType');
|
|
216
|
+
|
|
217
|
+
expect(output.metadata.totalMessages).toBe(3);
|
|
218
|
+
expect(output.metadata.operations).toContain('test');
|
|
219
|
+
expect(output.metadata.messagesByType.info).toBe(1);
|
|
220
|
+
expect(output.metadata.messagesByType.success).toBe(1);
|
|
221
|
+
expect(output.metadata.messagesByType.warn).toBe(1);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test('should save valid JSON', async () => {
|
|
225
|
+
messageCollector.startCollecting('test');
|
|
226
|
+
Logger.info('Message 1');
|
|
227
|
+
Logger.success('Message 2');
|
|
228
|
+
messageCollector.stopCollecting();
|
|
229
|
+
|
|
230
|
+
await messageCollector.saveMessages(testOutputPath);
|
|
231
|
+
|
|
232
|
+
// Should not throw when reading
|
|
233
|
+
const output = await fs.readJson(testOutputPath);
|
|
234
|
+
expect(output.messages).toHaveLength(2);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe('Edge Cases', () => {
|
|
239
|
+
test('should handle empty message collection', () => {
|
|
240
|
+
messageCollector.startCollecting('test');
|
|
241
|
+
messageCollector.stopCollecting();
|
|
242
|
+
|
|
243
|
+
const messages = messageCollector.getMessages();
|
|
244
|
+
expect(messages).toHaveLength(0);
|
|
245
|
+
|
|
246
|
+
const stats = messageCollector.getStatistics();
|
|
247
|
+
expect(stats.total).toBe(0);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test('should handle multiple start calls', () => {
|
|
251
|
+
messageCollector.startCollecting('operation1');
|
|
252
|
+
messageCollector.startCollecting('operation2');
|
|
253
|
+
|
|
254
|
+
Logger.info('Test');
|
|
255
|
+
|
|
256
|
+
const messages = messageCollector.getMessages();
|
|
257
|
+
expect(messages[0].operation).toBe('operation2'); // Last operation
|
|
258
|
+
|
|
259
|
+
messageCollector.stopCollecting();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('should handle special characters in messages', () => {
|
|
263
|
+
messageCollector.startCollecting('test');
|
|
264
|
+
Logger.info('Special chars: 😀 🔥 \n\t \\');
|
|
265
|
+
|
|
266
|
+
const messages = messageCollector.getMessages();
|
|
267
|
+
expect(messages[0].message).toContain('😀');
|
|
268
|
+
|
|
269
|
+
messageCollector.stopCollecting();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('should convert non-string messages to strings', () => {
|
|
273
|
+
messageCollector.startCollecting('test');
|
|
274
|
+
Logger.info({ nested: 'object' });
|
|
275
|
+
Logger.info(123);
|
|
276
|
+
Logger.info(null);
|
|
277
|
+
|
|
278
|
+
const messages = messageCollector.getMessages();
|
|
279
|
+
expect(typeof messages[0].message).toBe('string');
|
|
280
|
+
expect(typeof messages[1].message).toBe('string');
|
|
281
|
+
expect(typeof messages[2].message).toBe('string');
|
|
282
|
+
|
|
283
|
+
messageCollector.stopCollecting();
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
});
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Markdown-it Link Enhancement Module
|
|
5
|
-
*
|
|
6
|
-
* This module enhances link rendering by adding path-based attributes to anchor tags.
|
|
7
|
-
* These attributes can be used for CSS styling or JavaScript behavior based on the
|
|
8
|
-
* link's destination (domain, path segments, etc.).
|
|
9
|
-
*
|
|
10
|
-
* For example, a link to "https://example.com/docs/api" would get attributes like:
|
|
11
|
-
* - path-0="example.com"
|
|
12
|
-
* - path-1="docs"
|
|
13
|
-
* - path-2="api"
|
|
14
|
-
*
|
|
15
|
-
* This allows for targeted styling of links based on their destination.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Regular expression to extract domains and path segments from URLs
|
|
20
|
-
*
|
|
21
|
-
* This regex has two capture groups:
|
|
22
|
-
* - Group 1: Domain from http(s):// URLs (e.g., "example.com" from "https://example.com/path")
|
|
23
|
-
* - Group 2: Path segments from relative URLs (e.g., "docs" from "/docs/page")
|
|
24
|
-
*
|
|
25
|
-
* The 'g' flag enables global matching to find all segments in a URL.
|
|
26
|
-
*/
|
|
27
|
-
const pathSegmentRegex = /(?:http[s]*:\/\/([^\/]*)|(?:\/([^\/?]*)))/g;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Applies link enhancements to a markdown-it instance
|
|
31
|
-
*
|
|
32
|
-
* @param {Object} md - The markdown-it instance to enhance
|
|
33
|
-
*
|
|
34
|
-
* This function overrides the default link_open and link_close renderers
|
|
35
|
-
* to add path-based attributes and special handling for auto-detected links.
|
|
36
|
-
*/
|
|
37
|
-
function applyLinkEnhancements(md) {
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Custom link_open renderer that adds path attributes
|
|
41
|
-
*
|
|
42
|
-
* @param {Array} tokens - Array of all tokens being processed
|
|
43
|
-
* @param {Number} idx - Index of the current link_open token
|
|
44
|
-
* @param {Object} options - Markdown-it options
|
|
45
|
-
* @param {Object} env - Environment/context object
|
|
46
|
-
* @param {Object} renderer - The renderer instance
|
|
47
|
-
* @returns {String} HTML string for the opening anchor tag with path attributes
|
|
48
|
-
*/
|
|
49
|
-
md.renderer.rules.link_open = function (tokens, idx, options, env, renderer) {
|
|
50
|
-
let token = tokens[idx];
|
|
51
|
-
|
|
52
|
-
// Process all attributes of the link token
|
|
53
|
-
let attrs = token.attrs.reduce((str, attr) => {
|
|
54
|
-
let name = attr[0]; // Attribute name (e.g., 'href', 'title')
|
|
55
|
-
let value = attr[1]; // Attribute value (e.g., 'https://example.com')
|
|
56
|
-
|
|
57
|
-
// Special processing for href attributes to add path information
|
|
58
|
-
if (name === 'href') {
|
|
59
|
-
let index = 0;
|
|
60
|
-
|
|
61
|
-
// Extract domain and path segments using the regex
|
|
62
|
-
value.replace(pathSegmentRegex, (match, domain, pathSegment) => {
|
|
63
|
-
// Add path-N attributes for each segment found
|
|
64
|
-
// domain OR pathSegment will be defined (not both, due to regex groups)
|
|
65
|
-
str += `path-${index++}="${domain || pathSegment}" `;
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Add the original attribute to the string
|
|
70
|
-
str += `${name}="${value}" `;
|
|
71
|
-
return str;
|
|
72
|
-
}, '');
|
|
73
|
-
|
|
74
|
-
// Create the opening anchor tag with all attributes
|
|
75
|
-
let anchor = `<a ${attrs}>`;
|
|
76
|
-
|
|
77
|
-
// Special handling for auto-detected links (linkify plugin)
|
|
78
|
-
// These get an extra <span> wrapper for styling purposes
|
|
79
|
-
return token.markup === 'linkify' ? anchor + '<span>' : anchor;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Custom link_close renderer
|
|
84
|
-
*
|
|
85
|
-
* @param {Array} tokens - Array of all tokens being processed
|
|
86
|
-
* @param {Number} idx - Index of the current link_close token
|
|
87
|
-
* @param {Object} options - Markdown-it options
|
|
88
|
-
* @param {Object} env - Environment/context object
|
|
89
|
-
* @param {Object} renderer - The renderer instance
|
|
90
|
-
* @returns {String} HTML string for the closing anchor tag
|
|
91
|
-
*/
|
|
92
|
-
md.renderer.rules.link_close = function (tokens, idx, options, env, renderer) {
|
|
93
|
-
// Close the extra span for linkify links, or just close the anchor
|
|
94
|
-
return tokens[idx].markup === 'linkify' ? '</span></a>' : '</a>';
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
module.exports = applyLinkEnhancements;
|