spec-up-t 1.5.0 → 1.6.0-beta.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.
- package/assets/compiled/body.js +1 -0
- package/assets/compiled/head.css +3 -3
- package/assets/css/index.css +5 -4
- package/assets/css/refs.css +33 -0
- package/assets/css/terms-and-definitions.css +88 -0
- package/assets/js/insert-irefs.js +214 -0
- package/config/asset-map.json +2 -1
- package/package.json +1 -1
- package/src/create-docx.js +13 -6
- package/src/create-pdf.js +22 -18
- package/src/init.js +7 -3
- package/src/install-from-boilerplate/boilerplate/spec/terms-and-definitions-intro.md +1 -1
- package/src/parsers/template-tag-parser.js +32 -4
- package/src/pipeline/references/collect-external-references.js +41 -14
- package/src/pipeline/references/external-references-service.js +60 -24
- package/src/pipeline/references/process-xtrefs-data.js +43 -19
- package/src/utils/logger.js +116 -9
- package/src/utils/regex-patterns.js +25 -1
- package/test/logger.test.js +290 -0
- package/assets/css/insert-trefs.css +0 -1
- package/src/utils/LOGGER.md +0 -81
package/src/create-pdf.js
CHANGED
|
@@ -198,7 +198,7 @@ async function createTOCIfNeeded(page, logo, logoLink, title, description) {
|
|
|
198
198
|
// Insert the TOC after the title section
|
|
199
199
|
document.body.insertBefore(tocContainer, titleWrapper.nextSibling);
|
|
200
200
|
|
|
201
|
-
|
|
201
|
+
console.log('Generated a Table of Contents with ' + headings.length + ' entries.');
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
204
|
}, logo, logoLink, title, description);
|
|
@@ -254,7 +254,7 @@ async function createTOCIfNeeded(page, logo, logoLink, title, description) {
|
|
|
254
254
|
|
|
255
255
|
// If TOC doesn't exist, we'll need to create one
|
|
256
256
|
if (!document.getElementById('toc')) {
|
|
257
|
-
|
|
257
|
+
console.log('No TOC found in the document. Will create one.');
|
|
258
258
|
}
|
|
259
259
|
});
|
|
260
260
|
|
|
@@ -508,7 +508,7 @@ async function createTOCIfNeeded(page, logo, logoLink, title, description) {
|
|
|
508
508
|
}
|
|
509
509
|
});
|
|
510
510
|
|
|
511
|
-
|
|
511
|
+
Logger.process('Generating PDF with proper TOC page numbers...');
|
|
512
512
|
|
|
513
513
|
// First, generate a draft PDF to calculate the page positions of each heading
|
|
514
514
|
const draftPdfBuffer = await page.pdf({
|
|
@@ -530,28 +530,28 @@ async function createTOCIfNeeded(page, logo, logoLink, title, description) {
|
|
|
530
530
|
// Find the PDF TOC
|
|
531
531
|
const pdfToc = document.getElementById('pdf-toc');
|
|
532
532
|
if (!pdfToc) return;
|
|
533
|
-
|
|
533
|
+
|
|
534
534
|
const tocEntries = pdfToc.querySelectorAll('.toc-page-number');
|
|
535
535
|
const originalToc = document.getElementById('toc');
|
|
536
|
-
|
|
536
|
+
|
|
537
537
|
if (originalToc) {
|
|
538
538
|
// Get all links from the original TOC that have tooltip data
|
|
539
539
|
const originalLinks = originalToc.querySelectorAll('a[title], a[data-bs-title]');
|
|
540
|
-
|
|
540
|
+
|
|
541
541
|
// Create a mapping from heading IDs to page numbers based on tooltips
|
|
542
542
|
const idToPageMap = {};
|
|
543
|
-
|
|
543
|
+
|
|
544
544
|
originalLinks.forEach(link => {
|
|
545
545
|
// Extract the heading ID from href
|
|
546
546
|
const href = link.getAttribute('href');
|
|
547
547
|
if (!href || !href.startsWith('#')) return;
|
|
548
|
-
|
|
548
|
+
|
|
549
549
|
const headingId = href.substring(1);
|
|
550
|
-
|
|
550
|
+
|
|
551
551
|
// Extract page number from tooltip text (e.g., "Go to page 5")
|
|
552
552
|
const tooltipText = link.getAttribute('title') || link.getAttribute('data-bs-title');
|
|
553
553
|
if (!tooltipText) return;
|
|
554
|
-
|
|
554
|
+
|
|
555
555
|
const pageNumberMatch = tooltipText.match(/Go to page (\d+)/i);
|
|
556
556
|
if (pageNumberMatch && pageNumberMatch[1]) {
|
|
557
557
|
const pageNumber = parseInt(pageNumberMatch[1], 10);
|
|
@@ -560,7 +560,7 @@ async function createTOCIfNeeded(page, logo, logoLink, title, description) {
|
|
|
560
560
|
}
|
|
561
561
|
}
|
|
562
562
|
});
|
|
563
|
-
|
|
563
|
+
|
|
564
564
|
// Now update the TOC page numbers using the extracted values
|
|
565
565
|
tocEntries.forEach(entry => {
|
|
566
566
|
const targetId = entry.getAttribute('data-for-id');
|
|
@@ -578,10 +578,10 @@ async function createTOCIfNeeded(page, logo, logoLink, title, description) {
|
|
|
578
578
|
} else {
|
|
579
579
|
// Fallback to old estimation method if original TOC is not available
|
|
580
580
|
console.log('Original TOC not found, using page number estimation method');
|
|
581
|
-
|
|
581
|
+
|
|
582
582
|
// Find all headings with IDs (potential TOC targets)
|
|
583
583
|
const headingsWithIds = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6')).filter(h => h.id);
|
|
584
|
-
|
|
584
|
+
|
|
585
585
|
// Use real offsets for more accurate page numbers
|
|
586
586
|
const idToPosition = {};
|
|
587
587
|
|
|
@@ -643,12 +643,12 @@ async function createTOCIfNeeded(page, logo, logoLink, title, description) {
|
|
|
643
643
|
|
|
644
644
|
await browser.close();
|
|
645
645
|
|
|
646
|
-
|
|
646
|
+
Logger.success('PDF generated by Puppeteer. Processing with pdf-lib for ISO compliance...');
|
|
647
647
|
|
|
648
648
|
// Optimize PDF with pdf-lib for ISO compliance
|
|
649
649
|
try {
|
|
650
650
|
const pdfDoc = await pdfLib.PDFDocument.load(pdfBuffer);
|
|
651
|
-
|
|
651
|
+
|
|
652
652
|
// Set ISO-compliant metadata (this is safer than XMP embedding)
|
|
653
653
|
pdfDoc.setTitle(metadata.title);
|
|
654
654
|
pdfDoc.setAuthor(metadata.author);
|
|
@@ -666,7 +666,7 @@ async function createTOCIfNeeded(page, logo, logoLink, title, description) {
|
|
|
666
666
|
useObjectStreams: false, // Required for PDF/A compliance
|
|
667
667
|
addDefaultPage: false
|
|
668
668
|
});
|
|
669
|
-
|
|
669
|
+
|
|
670
670
|
fs.writeFileSync('docs/index.pdf', optimizedPdfBytes);
|
|
671
671
|
Logger.success('PDF saved with ISO compliance features.');
|
|
672
672
|
} catch (pdfError) {
|
|
@@ -675,8 +675,12 @@ async function createTOCIfNeeded(page, logo, logoLink, title, description) {
|
|
|
675
675
|
fs.writeFileSync('docs/index.pdf', pdfBuffer);
|
|
676
676
|
}
|
|
677
677
|
|
|
678
|
-
|
|
678
|
+
Logger.success('PDF generated successfully! Find the PDF in the docs directory.');
|
|
679
679
|
} catch (error) {
|
|
680
|
-
Logger.error('Error generating PDF
|
|
680
|
+
Logger.error('Error generating PDF', {
|
|
681
|
+
context: 'Failed during PDF document generation',
|
|
682
|
+
hint: 'Ensure the HTML file exists (run "npm run render" first), Puppeteer is installed, and you have write permissions',
|
|
683
|
+
details: error.message || error
|
|
684
|
+
});
|
|
681
685
|
}
|
|
682
686
|
})();
|
package/src/init.js
CHANGED
|
@@ -8,14 +8,14 @@ async function initialize() {
|
|
|
8
8
|
try {
|
|
9
9
|
// Ensure the .cache directory exists
|
|
10
10
|
await fs.ensureDir(outputDir);
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
// Check if the init script has already run
|
|
13
13
|
if (await fs.pathExists(initFlagPath)) {
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// Place the init script here
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
// End of the init script
|
|
20
20
|
|
|
21
21
|
// Create the init flag file
|
|
@@ -23,7 +23,11 @@ async function initialize() {
|
|
|
23
23
|
|
|
24
24
|
Logger.success('Initialization complete.');
|
|
25
25
|
} catch (error) {
|
|
26
|
-
Logger.error(
|
|
26
|
+
Logger.error('Initialization failed', {
|
|
27
|
+
context: 'Failed to set up spec-up-t boilerplate files',
|
|
28
|
+
hint: 'Ensure you have write permissions in the current directory. Try running with appropriate permissions or in a different directory',
|
|
29
|
+
details: error.message
|
|
30
|
+
});
|
|
27
31
|
}
|
|
28
32
|
}
|
|
29
33
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
[//]: # (This file, named “terms-and-definitions-intro.md” is mandatory and should not be deleted.)
|
|
1
|
+
[//]: # (This file, named “terms-and-definitions-intro.md” is mandatory and should not be deleted. However, you can safely delete this comment and replace it with text of your choice.)
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
const { findExternalSpecByKey } = require('../pipeline/references/external-references-service.js');
|
|
15
15
|
const { lookupXrefTerm } = require('../pipeline/rendering/render-utils.js');
|
|
16
|
-
const { whitespace, htmlComments, contentCleaning, externalReferences } = require('../utils/regex-patterns');
|
|
16
|
+
const { whitespace, htmlComments, contentCleaning, externalReferences, utils } = require('../utils/regex-patterns');
|
|
17
17
|
const Logger = require('../utils/logger.js');
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -33,7 +33,7 @@ function extractCurrentFile(token, globalState) {
|
|
|
33
33
|
* @param {Object} config - Configuration object containing specs and settings
|
|
34
34
|
* @param {Object} globalState - Global state object containing definitions, references, etc.
|
|
35
35
|
* @param {Object} token - The markdown-it token being processed
|
|
36
|
-
* @param {string} type - The type of construct (def, ref, xref, tref)
|
|
36
|
+
* @param {string} type - The type of construct (def, ref, iref, xref, tref)
|
|
37
37
|
* @param {string} primary - The primary content/term
|
|
38
38
|
* @returns {string} The rendered HTML for the construct
|
|
39
39
|
*/
|
|
@@ -45,6 +45,8 @@ function parseTemplateTag(config, globalState, token, type, primary) {
|
|
|
45
45
|
switch (type) {
|
|
46
46
|
case 'def':
|
|
47
47
|
return parseDef(globalState, token, primary, currentFile);
|
|
48
|
+
case 'iref':
|
|
49
|
+
return parseIref(globalState, primary);
|
|
48
50
|
case 'xref':
|
|
49
51
|
return parseXref(config, token);
|
|
50
52
|
case 'tref':
|
|
@@ -109,7 +111,10 @@ function parseDef(globalState, token, primary, currentFile) {
|
|
|
109
111
|
// IDs stay intact - we create an ID for the original term and each alias
|
|
110
112
|
return token.info.args.reduce((acc, syn) => {
|
|
111
113
|
// Generate a unique term ID by normalizing the synonym: replace whitespace with hyphens and convert to lowercase. The ID is used for fragment identifier (hash) in the URL, which in turn can be used for an anchor in a web page.
|
|
112
|
-
|
|
114
|
+
// Apply sanitization to remove special characters that would break CSS selectors
|
|
115
|
+
const normalizedSyn = syn.replace(whitespace.oneOrMore, '-').toLowerCase();
|
|
116
|
+
const sanitizedSyn = utils.sanitizeTermId(normalizedSyn);
|
|
117
|
+
const termId = `term:${sanitizedSyn}`;
|
|
113
118
|
return `<span id="${termId}">${acc}</span>`;
|
|
114
119
|
}, displayText);
|
|
115
120
|
}
|
|
@@ -130,6 +135,25 @@ function parseRef(globalState, primary) {
|
|
|
130
135
|
return `<a class="term-reference" href="#term:${termId}">${primary}</a>`;
|
|
131
136
|
}
|
|
132
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Processes [[iref: term]] constructs
|
|
140
|
+
* Creates a placeholder that will be replaced client-side with a copy of the term definition
|
|
141
|
+
* This allows inline copying of existing term definitions from the terms-and-definitions-list
|
|
142
|
+
* @param {Object} globalState - Global state to track inline references
|
|
143
|
+
* @param {string} primary - The term to inline copy
|
|
144
|
+
* @returns {string} HTML placeholder element that will be replaced by client-side script
|
|
145
|
+
*/
|
|
146
|
+
function parseIref(globalState, primary) {
|
|
147
|
+
// Track this inline reference for validation purposes
|
|
148
|
+
globalState.references.push(primary);
|
|
149
|
+
|
|
150
|
+
// Create a placeholder span with data attribute containing the term to copy
|
|
151
|
+
// The client-side script (insert-irefs.js) will find this and replace it with
|
|
152
|
+
// a copy of the actual <dt> and <dd> elements from the terms-and-definitions-list
|
|
153
|
+
const termId = primary.replace(whitespace.oneOrMore, '-').toLowerCase();
|
|
154
|
+
return `<span class="iref-placeholder" data-iref-term="${termId}" data-iref-original="${primary}"></span>`;
|
|
155
|
+
}
|
|
156
|
+
|
|
133
157
|
/**
|
|
134
158
|
* Processes [[xref: spec, term, alias, ...]] constructs
|
|
135
159
|
* Creates links to external specification terms with tooltips
|
|
@@ -217,7 +241,10 @@ function parseTref(token) {
|
|
|
217
241
|
|
|
218
242
|
return termsAndAliases.reduce((acc, syn, index) => {
|
|
219
243
|
// Generate a unique term ID by normalizing the synonym: replace whitespace with hyphens and convert to lowercase
|
|
220
|
-
|
|
244
|
+
// Apply sanitization to remove special characters that would break CSS selectors
|
|
245
|
+
const normalizedSyn = syn.replace(whitespace.oneOrMore, '-').toLowerCase();
|
|
246
|
+
const sanitizedSyn = utils.sanitizeTermId(normalizedSyn);
|
|
247
|
+
const termId = `term:${sanitizedSyn}`;
|
|
221
248
|
// Add title attribute to the innermost span (first in the array, which wraps the display text directly)
|
|
222
249
|
// This provides a tooltip showing which external term this alias refers to
|
|
223
250
|
const titleAttr = index === 0 && aliases.length > 0 ? ` title="Externally defined as ${termName}"` : '';
|
|
@@ -298,6 +325,7 @@ module.exports = {
|
|
|
298
325
|
createTemplateTagParser,
|
|
299
326
|
// Export individual functions for testing purposes
|
|
300
327
|
parseDef,
|
|
328
|
+
parseIref,
|
|
301
329
|
parseXref,
|
|
302
330
|
parseTref,
|
|
303
331
|
parseRef,
|
|
@@ -46,7 +46,11 @@ function normalizeSpecConfiguration(config, { noSpecsMessage }) {
|
|
|
46
46
|
const specs = Array.isArray(config?.specs) ? config.specs : [];
|
|
47
47
|
|
|
48
48
|
if (specs.length === 0) {
|
|
49
|
-
Logger.error(noSpecsMessage
|
|
49
|
+
Logger.error(noSpecsMessage, {
|
|
50
|
+
context: 'specs.json is missing or has no specs array',
|
|
51
|
+
hint: 'Create a valid specs.json file in your project root. Run "npm run init" or copy from: https://github.com/trustoverip/spec-up-t-starter-pack',
|
|
52
|
+
details: 'The specs array is required to configure your specification'
|
|
53
|
+
});
|
|
50
54
|
return null;
|
|
51
55
|
}
|
|
52
56
|
|
|
@@ -65,7 +69,11 @@ function normalizeSpecConfiguration(config, { noSpecsMessage }) {
|
|
|
65
69
|
*/
|
|
66
70
|
function extendXTrefs(config, xtrefs) {
|
|
67
71
|
if (config.specs[0].external_specs_repos) {
|
|
68
|
-
Logger.warn('Your specs.json file uses an outdated structure
|
|
72
|
+
Logger.warn('Your specs.json file uses an outdated structure', {
|
|
73
|
+
context: 'The "external_specs_repos" field is deprecated',
|
|
74
|
+
hint: 'Update to the new structure using "external_specs" instead. See: https://github.com/trustoverip/spec-up-t/blob/master/src/install-from-boilerplate/boilerplate/specs.json',
|
|
75
|
+
details: 'External references may not work correctly with the old structure'
|
|
76
|
+
});
|
|
69
77
|
return;
|
|
70
78
|
}
|
|
71
79
|
|
|
@@ -118,7 +126,11 @@ function extendXTrefs(config, xtrefs) {
|
|
|
118
126
|
try {
|
|
119
127
|
xtref.branch = getCurrentBranch();
|
|
120
128
|
} catch (error) {
|
|
121
|
-
Logger.warn(`Could not get current branch for xtref ${xtref.externalSpec}:${xtref.term}
|
|
129
|
+
Logger.warn(`Could not get current branch for xtref ${xtref.externalSpec}:${xtref.term}`, {
|
|
130
|
+
context: 'Git branch detection failed for external reference',
|
|
131
|
+
hint: 'Ensure you\'re in a git repository, or specify github_repo_branch in specs.json external_specs configuration',
|
|
132
|
+
details: `${error.message}. Using default: main`
|
|
133
|
+
});
|
|
122
134
|
xtref.branch = 'main';
|
|
123
135
|
}
|
|
124
136
|
});
|
|
@@ -153,7 +165,7 @@ function processExternalReferences(config, GITHUB_API_TOKEN) {
|
|
|
153
165
|
}
|
|
154
166
|
|
|
155
167
|
const userInput = readlineSync.question(
|
|
156
|
-
`❌ This external reference is not a valid URL:
|
|
168
|
+
`❌ This external reference is not a valid URL:
|
|
157
169
|
|
|
158
170
|
Repository: ${repo.url},
|
|
159
171
|
|
|
@@ -195,14 +207,22 @@ function processExternalReferences(config, GITHUB_API_TOKEN) {
|
|
|
195
207
|
const termsDir = spec?.spec_terms_directory;
|
|
196
208
|
|
|
197
209
|
if (!specDir || !termsDir) {
|
|
198
|
-
Logger.warn(`Spec entry is missing spec_directory or spec_terms_directory
|
|
210
|
+
Logger.warn(`Spec entry is missing spec_directory or spec_terms_directory`, {
|
|
211
|
+
context: 'Invalid specs.json configuration',
|
|
212
|
+
hint: 'Ensure each spec in specs.json has both "spec_directory" and "spec_terms_directory" fields',
|
|
213
|
+
details: `Incomplete spec entry: ${JSON.stringify(spec)}`
|
|
214
|
+
});
|
|
199
215
|
return directories;
|
|
200
216
|
}
|
|
201
217
|
|
|
202
218
|
const resolvedDir = path.join(specDir, termsDir);
|
|
203
219
|
|
|
204
220
|
if (!fs.existsSync(resolvedDir)) {
|
|
205
|
-
Logger.warn(`Spec terms directory does not exist: ${resolvedDir}
|
|
221
|
+
Logger.warn(`Spec terms directory does not exist: ${resolvedDir}`, {
|
|
222
|
+
context: 'Directory specified in specs.json not found',
|
|
223
|
+
hint: 'Create the directory or update the path in specs.json. Typically this should be "spec/term-definitions" or similar',
|
|
224
|
+
details: `Expected path: ${resolvedDir}`
|
|
225
|
+
});
|
|
206
226
|
return directories;
|
|
207
227
|
}
|
|
208
228
|
|
|
@@ -211,7 +231,11 @@ function processExternalReferences(config, GITHUB_API_TOKEN) {
|
|
|
211
231
|
}, []);
|
|
212
232
|
|
|
213
233
|
if (specTermsDirectories.length === 0) {
|
|
214
|
-
Logger.warn('No spec terms directories found. Skipping external reference collection
|
|
234
|
+
Logger.warn('No spec terms directories found. Skipping external reference collection', {
|
|
235
|
+
context: 'Cannot collect external references without valid terminology directories',
|
|
236
|
+
hint: 'Check specs.json configuration. Ensure spec_directory and spec_terms_directory point to existing directories',
|
|
237
|
+
details: 'External trefs and xrefs will not be processed'
|
|
238
|
+
});
|
|
215
239
|
return;
|
|
216
240
|
}
|
|
217
241
|
|
|
@@ -249,7 +273,7 @@ function processExternalReferences(config, GITHUB_API_TOKEN) {
|
|
|
249
273
|
function collectExternalReferences(options = {}) {
|
|
250
274
|
// Start collecting messages if requested
|
|
251
275
|
const shouldCollectMessages = options.collectMessages !== false; // Collect by default
|
|
252
|
-
|
|
276
|
+
|
|
253
277
|
if (shouldCollectMessages) {
|
|
254
278
|
messageCollector.clearMessages();
|
|
255
279
|
messageCollector.startCollecting('collectExternalReferences');
|
|
@@ -273,8 +297,11 @@ function collectExternalReferences(options = {}) {
|
|
|
273
297
|
const GITHUB_API_TOKEN = options.pat || process.env.GITHUB_API_TOKEN;
|
|
274
298
|
|
|
275
299
|
if (!GITHUB_API_TOKEN) {
|
|
276
|
-
Logger.warn('No GitHub Personal Access Token (PAT) found. Running without authentication
|
|
277
|
-
|
|
300
|
+
Logger.warn('No GitHub Personal Access Token (PAT) found. Running without authentication', {
|
|
301
|
+
context: 'GitHub API requests will use unauthenticated access',
|
|
302
|
+
hint: 'Set GITHUB_PAT environment variable to increase rate limit from 60 to 5000 requests/hour. See: https://trustoverip.github.io/spec-up-t-website/docs/getting-started/github-token',
|
|
303
|
+
details: 'You may hit rate limits when fetching many external references'
|
|
304
|
+
});
|
|
278
305
|
}
|
|
279
306
|
|
|
280
307
|
// Communicate that the expected external_specs array is missing entirely.
|
|
@@ -282,7 +309,7 @@ function collectExternalReferences(options = {}) {
|
|
|
282
309
|
Logger.info(
|
|
283
310
|
'No external_specs array found on the first spec entry in specs.json. External reference collection is skipped.'
|
|
284
311
|
);
|
|
285
|
-
|
|
312
|
+
|
|
286
313
|
if (shouldCollectMessages) {
|
|
287
314
|
messageCollector.stopCollecting();
|
|
288
315
|
messageCollector.saveMessages().then(path => {
|
|
@@ -300,7 +327,7 @@ function collectExternalReferences(options = {}) {
|
|
|
300
327
|
Logger.info(
|
|
301
328
|
'The external_specs array in specs.json is empty. Add external repositories to collect external references.'
|
|
302
329
|
);
|
|
303
|
-
|
|
330
|
+
|
|
304
331
|
if (shouldCollectMessages) {
|
|
305
332
|
messageCollector.stopCollecting();
|
|
306
333
|
messageCollector.saveMessages().then(path => {
|
|
@@ -333,14 +360,14 @@ function collectExternalReferences(options = {}) {
|
|
|
333
360
|
})
|
|
334
361
|
.catch(error => {
|
|
335
362
|
Logger.error('Rendering failed after collecting external references.', error);
|
|
336
|
-
|
|
363
|
+
|
|
337
364
|
if (shouldCollectMessages) {
|
|
338
365
|
messageCollector.stopCollecting();
|
|
339
366
|
messageCollector.saveMessages().catch(() => {
|
|
340
367
|
// Silent fail on save error
|
|
341
368
|
});
|
|
342
369
|
}
|
|
343
|
-
|
|
370
|
+
|
|
344
371
|
throw error;
|
|
345
372
|
});
|
|
346
373
|
} else {
|
|
@@ -17,7 +17,11 @@ function validateReferences(references, definitions, render) {
|
|
|
17
17
|
}
|
|
18
18
|
);
|
|
19
19
|
if (unresolvedRefs.length > 0) {
|
|
20
|
-
Logger.warn(`Unresolved References: ${unresolvedRefs.join(',')}
|
|
20
|
+
Logger.warn(`Unresolved References: ${unresolvedRefs.join(',')}`, {
|
|
21
|
+
context: 'These terms are referenced in your spec but not defined',
|
|
22
|
+
hint: 'Add [[def: term]] definitions for these terms in your terminology files, or check for typos in [[ref: term]] references',
|
|
23
|
+
details: `Count: ${unresolvedRefs.length} unresolved term(s)`
|
|
24
|
+
});
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
const danglingDefs = [];
|
|
@@ -38,7 +42,11 @@ function validateReferences(references, definitions, render) {
|
|
|
38
42
|
}
|
|
39
43
|
})
|
|
40
44
|
if (danglingDefs.length > 0) {
|
|
41
|
-
Logger.warn(`Dangling Definitions: ${danglingDefs.join(',')}
|
|
45
|
+
Logger.warn(`Dangling Definitions: ${danglingDefs.join(',')}`, {
|
|
46
|
+
context: 'These terms are defined but never referenced in your spec',
|
|
47
|
+
hint: 'Add [[ref: term]] references where needed.',
|
|
48
|
+
details: `Count: ${danglingDefs.length} unused definition(s)`
|
|
49
|
+
});
|
|
42
50
|
}
|
|
43
51
|
}
|
|
44
52
|
|
|
@@ -71,7 +79,11 @@ async function fetchExternalSpecs(spec) {
|
|
|
71
79
|
const msg = f.error.response
|
|
72
80
|
? `HTTP ${f.error.response.status} for ${f.url}`
|
|
73
81
|
: `Network error for ${f.url}: ${f.error.message}`;
|
|
74
|
-
Logger.error("External spec fetch failed
|
|
82
|
+
Logger.error("External spec fetch failed", {
|
|
83
|
+
context: `Attempting to fetch external terminology from: ${f.url}`,
|
|
84
|
+
hint: 'Verify the URL is correct in specs.json under external_specs[].gh_page. Ensure the external spec has published their GitHub Pages site',
|
|
85
|
+
details: msg
|
|
86
|
+
});
|
|
75
87
|
});
|
|
76
88
|
}
|
|
77
89
|
|
|
@@ -93,7 +105,11 @@ async function fetchExternalSpecs(spec) {
|
|
|
93
105
|
|
|
94
106
|
return extractedTerms;
|
|
95
107
|
} catch (e) {
|
|
96
|
-
Logger.error("Unexpected error in fetchExternalSpecs
|
|
108
|
+
Logger.error("Unexpected error in fetchExternalSpecs", {
|
|
109
|
+
context: 'Failed while fetching terms from external specifications',
|
|
110
|
+
hint: 'Check your internet connection and verify all external_specs URLs in specs.json are valid. Run with GITHUB_PAT set if accessing private repos',
|
|
111
|
+
details: e.message
|
|
112
|
+
});
|
|
97
113
|
return [];
|
|
98
114
|
}
|
|
99
115
|
}
|
|
@@ -132,7 +148,7 @@ async function mergeXrefTermsIntoAllXTrefs(xrefTerms, outputPathJSON, outputPath
|
|
|
132
148
|
if (existingIndex >= 0) {
|
|
133
149
|
// Get the existing entry to check if it's a tref
|
|
134
150
|
const existingXtref = allXTrefs.xtrefs[existingIndex];
|
|
135
|
-
|
|
151
|
+
|
|
136
152
|
// Update existing entry - preserve the existing metadata but add/update content
|
|
137
153
|
allXTrefs.xtrefs[existingIndex] = {
|
|
138
154
|
...existingXtref,
|
|
@@ -151,26 +167,34 @@ async function mergeXrefTermsIntoAllXTrefs(xrefTerms, outputPathJSON, outputPath
|
|
|
151
167
|
|
|
152
168
|
if (isExternalTref && isTref) {
|
|
153
169
|
// Build a readable list of source files for the error message
|
|
154
|
-
const sourceFilesList = existingXtref.sourceFile
|
|
155
|
-
? existingXtref.sourceFile
|
|
170
|
+
const sourceFilesList = existingXtref.sourceFile
|
|
171
|
+
? existingXtref.sourceFile
|
|
156
172
|
: (existingXtref.sourceFiles || []).map(sf => sf.file).join(', ');
|
|
157
|
-
|
|
173
|
+
|
|
158
174
|
// Construct the external repository URL
|
|
159
175
|
const externalRepoUrl = existingXtref.ghPageUrl || existingXtref.repoUrl || `https://github.com/${existingXtref.owner}/${existingXtref.repo}`;
|
|
160
|
-
|
|
161
|
-
Logger.error(`
|
|
176
|
+
|
|
177
|
+
Logger.error(`NESTED TREF DETECTED: Term "${existingXtref.term}" in ${existingXtref.externalSpec}`, {
|
|
178
|
+
context: `Origin: ${sourceFilesList} - This term is itself transcluded from another spec`,
|
|
179
|
+
hint: 'Avoid chaining external references (tref → tref). Reference the original source spec directly, or define the term locally in your spec',
|
|
180
|
+
details: `This creates a complex dependency chain. Repository: ${externalRepoUrl}`
|
|
181
|
+
});
|
|
162
182
|
}
|
|
163
183
|
|
|
164
184
|
if (isExternalTref && isXref) {
|
|
165
185
|
// Build a readable list of source files for the warning message
|
|
166
|
-
const sourceFilesList = existingXtref.sourceFile
|
|
167
|
-
? existingXtref.sourceFile
|
|
186
|
+
const sourceFilesList = existingXtref.sourceFile
|
|
187
|
+
? existingXtref.sourceFile
|
|
168
188
|
: (existingXtref.sourceFiles || []).map(sf => sf.file).join(', ');
|
|
169
|
-
|
|
189
|
+
|
|
170
190
|
// Construct the external repository URL
|
|
171
191
|
const externalRepoUrl = existingXtref.ghPageUrl || existingXtref.repoUrl || `https://github.com/${existingXtref.owner}/${existingXtref.repo}`;
|
|
172
|
-
|
|
173
|
-
Logger.error(`
|
|
192
|
+
|
|
193
|
+
Logger.error(`NESTED XREF DETECTED: Term "${existingXtref.term}" in ${existingXtref.externalSpec}`, {
|
|
194
|
+
context: `Origin: ${sourceFilesList} - This xref points to a term that is already transcluded from elsewhere`,
|
|
195
|
+
hint: 'Use [[xref]] only for terms directly defined in the external spec. For nested references, either reference the original source or define the term locally',
|
|
196
|
+
details: `This creates a chain of external references. Repository: ${externalRepoUrl}`
|
|
197
|
+
});
|
|
174
198
|
}
|
|
175
199
|
|
|
176
200
|
matchedCount++;
|
|
@@ -191,7 +215,11 @@ async function mergeXrefTermsIntoAllXTrefs(xrefTerms, outputPathJSON, outputPath
|
|
|
191
215
|
Logger.success(`Merged xref terms: ${matchedCount} matched, ${skippedCount} skipped (not referenced). Total entries: ${allXTrefs.xtrefs.length}`);
|
|
192
216
|
|
|
193
217
|
} catch (error) {
|
|
194
|
-
Logger.error('Error merging xref terms into allXTrefs
|
|
218
|
+
Logger.error('Error merging xref terms into allXTrefs', {
|
|
219
|
+
context: 'Failed while merging external reference terms into the xtrefs cache',
|
|
220
|
+
hint: 'This may indicate corrupted cache files in .cache/xtrefs/. Try deleting the .cache directory and running the build again',
|
|
221
|
+
details: error.message
|
|
222
|
+
});
|
|
195
223
|
}
|
|
196
224
|
}
|
|
197
225
|
|
|
@@ -219,18 +247,18 @@ function extractTermsFromHtml(externalSpec, html) {
|
|
|
219
247
|
batch.each((index, termElement) => {
|
|
220
248
|
try {
|
|
221
249
|
const $termElement = $(termElement);
|
|
222
|
-
|
|
250
|
+
|
|
223
251
|
// The id can be on the dt element itself OR on span(s) inside it
|
|
224
252
|
// Some terms have multiple nested spans with different IDs (e.g., aliases)
|
|
225
253
|
// We need to extract ALL term IDs from the dt element
|
|
226
254
|
const termIds = [];
|
|
227
|
-
|
|
255
|
+
|
|
228
256
|
// First check if dt itself has an id
|
|
229
257
|
const dtId = $termElement.attr('id');
|
|
230
258
|
if (dtId && dtId.includes('term:')) {
|
|
231
259
|
termIds.push(dtId.replace('term:', ''));
|
|
232
260
|
}
|
|
233
|
-
|
|
261
|
+
|
|
234
262
|
// Then find all spans with ids that contain 'term:'
|
|
235
263
|
$termElement.find('span[id*="term:"]').each((i, span) => {
|
|
236
264
|
const spanId = $(span).attr('id');
|
|
@@ -241,7 +269,7 @@ function extractTermsFromHtml(externalSpec, html) {
|
|
|
241
269
|
}
|
|
242
270
|
}
|
|
243
271
|
});
|
|
244
|
-
|
|
272
|
+
|
|
245
273
|
// Skip if no valid term IDs found
|
|
246
274
|
if (termIds.length === 0) {
|
|
247
275
|
return;
|
|
@@ -252,12 +280,12 @@ function extractTermsFromHtml(externalSpec, html) {
|
|
|
252
280
|
const dtClasses = $termElement.attr('class');
|
|
253
281
|
const classArray = dtClasses ? dtClasses.split(/\s+/).filter(Boolean) : [];
|
|
254
282
|
const termClasses = classArray.filter(cls => cls === 'term-local' || cls === 'term-external');
|
|
255
|
-
|
|
283
|
+
|
|
256
284
|
const dd = $termElement.next('dd');
|
|
257
285
|
|
|
258
286
|
if (dd.length > 0) {
|
|
259
287
|
const ddContent = $.html(dd); // Store the complete DD content once
|
|
260
|
-
|
|
288
|
+
|
|
261
289
|
// Create a term object for each ID found in this dt element
|
|
262
290
|
// This handles cases where one term definition has multiple aliases
|
|
263
291
|
termIds.forEach(termName => {
|
|
@@ -275,7 +303,11 @@ function extractTermsFromHtml(externalSpec, html) {
|
|
|
275
303
|
});
|
|
276
304
|
}
|
|
277
305
|
} catch (termError) {
|
|
278
|
-
Logger.warn(`Error processing term in ${externalSpec}
|
|
306
|
+
Logger.warn(`Error processing term in ${externalSpec}`, {
|
|
307
|
+
context: 'Failed to extract a specific term from external spec HTML',
|
|
308
|
+
hint: 'The external spec may have malformed HTML or non-standard term definitions. Contact the spec maintainer if this persists',
|
|
309
|
+
details: termError.message
|
|
310
|
+
});
|
|
279
311
|
}
|
|
280
312
|
});
|
|
281
313
|
|
|
@@ -289,7 +321,11 @@ function extractTermsFromHtml(externalSpec, html) {
|
|
|
289
321
|
return terms;
|
|
290
322
|
|
|
291
323
|
} catch (error) {
|
|
292
|
-
Logger.error(`Error extracting terms from external spec '${externalSpec}'
|
|
324
|
+
Logger.error(`Error extracting terms from external spec '${externalSpec}'`, {
|
|
325
|
+
context: 'Failed while parsing HTML from external spec to extract term definitions',
|
|
326
|
+
hint: 'Verify that the external spec is a valid spec-up-t generated document with a proper terms-and-definitions section',
|
|
327
|
+
details: error.message
|
|
328
|
+
});
|
|
293
329
|
return [];
|
|
294
330
|
}
|
|
295
331
|
}
|