spec-up-t 1.4.1 → 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.
- package/assets/compiled/body.js +3 -2
- package/assets/compiled/head.css +5 -5
- package/assets/css/embedded-libraries/bootstrap.min.css +1 -1
- package/assets/css/header-navbar.css +4 -4
- package/assets/css/index.css +5 -4
- package/assets/css/refs.css +30 -0
- package/assets/css/terms-and-definitions.css +89 -1
- package/assets/js/github-issues.js +3 -3
- package/assets/js/insert-irefs.js +214 -0
- package/config/asset-map.json +2 -1
- 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/create-docx.js +13 -6
- package/src/create-pdf.js +22 -18
- package/src/health-check.js +47 -629
- package/src/init.js +7 -3
- 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 +42 -4
- package/src/pipeline/postprocessing/definition-list-postprocessor.js +4 -2
- package/src/pipeline/references/collect-external-references.js +101 -17
- package/src/pipeline/references/external-references-service.js +102 -21
- package/src/pipeline/references/fetch-terms-from-index.js +62 -1
- package/src/pipeline/references/process-xtrefs-data.js +67 -9
- 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 +129 -8
- package/src/utils/message-collector.js +144 -0
- package/src/utils/regex-patterns.js +3 -1
- package/templates/template.html +6 -6
- package/test/logger.test.js +290 -0
- package/test/message-collector.test.js +286 -0
- package/assets/css/insert-trefs.css +0 -1
- package/src/markdown-it/link-enhancement.js +0 -98
- package/src/utils/LOGGER.md +0 -81
|
@@ -17,7 +17,11 @@ function validateReferences(references, definitions, render) {
|
|
|
17
17
|
}
|
|
18
18
|
);
|
|
19
19
|
if (unresolvedRefs.length > 0) {
|
|
20
|
-
Logger.
|
|
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.
|
|
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
|
}
|
|
@@ -117,6 +133,10 @@ async function mergeXrefTermsIntoAllXTrefs(xrefTerms, outputPathJSON, outputPath
|
|
|
117
133
|
|
|
118
134
|
// Add xref terms to the allXTrefs structure
|
|
119
135
|
// Mark them with source: 'xref' to distinguish from tref entries
|
|
136
|
+
// Track how many terms were matched vs skipped for logging purposes
|
|
137
|
+
let matchedCount = 0;
|
|
138
|
+
let skippedCount = 0;
|
|
139
|
+
|
|
120
140
|
xrefTerms.forEach(xrefTerm => {
|
|
121
141
|
// Check if this term already exists (match by externalSpec and term only)
|
|
122
142
|
// Don't filter by source because entries from markdown scanning don't have source field
|
|
@@ -126,20 +146,62 @@ async function mergeXrefTermsIntoAllXTrefs(xrefTerms, outputPathJSON, outputPath
|
|
|
126
146
|
);
|
|
127
147
|
|
|
128
148
|
if (existingIndex >= 0) {
|
|
149
|
+
// Get the existing entry to check if it's a tref
|
|
150
|
+
const existingXtref = allXTrefs.xtrefs[existingIndex];
|
|
151
|
+
|
|
129
152
|
// Update existing entry - preserve the existing metadata but add/update content
|
|
130
153
|
allXTrefs.xtrefs[existingIndex] = {
|
|
131
|
-
...
|
|
154
|
+
...existingXtref,
|
|
132
155
|
content: xrefTerm.content, // Update the content from fetched HTML
|
|
156
|
+
classes: xrefTerm.classes || [], // Update classes from dt element
|
|
133
157
|
source: xrefTerm.source, // Add source field
|
|
134
158
|
termId: xrefTerm.termId, // Add termId if not present
|
|
135
159
|
lastUpdated: new Date().toISOString()
|
|
136
160
|
};
|
|
161
|
+
|
|
162
|
+
// Check if this is a tref to an external tref (nested tref)
|
|
163
|
+
// A term with 'term-external' class means it's transcluded from another spec
|
|
164
|
+
const isExternalTref = xrefTerm.classes && xrefTerm.classes.includes('term-external');
|
|
165
|
+
const isTref = existingXtref.sourceFiles && existingXtref.sourceFiles.some(sf => sf.type === 'tref');
|
|
166
|
+
const isXref = existingXtref.sourceFiles && existingXtref.sourceFiles.some(sf => sf.type === 'xref');
|
|
167
|
+
|
|
168
|
+
if (isExternalTref && isTref) {
|
|
169
|
+
// Build a readable list of source files for the error message
|
|
170
|
+
const sourceFilesList = existingXtref.sourceFile
|
|
171
|
+
? existingXtref.sourceFile
|
|
172
|
+
: (existingXtref.sourceFiles || []).map(sf => sf.file).join(', ');
|
|
173
|
+
|
|
174
|
+
// Construct the external repository URL
|
|
175
|
+
const externalRepoUrl = existingXtref.ghPageUrl || existingXtref.repoUrl || `https://github.com/${existingXtref.owner}/${existingXtref.repo}`;
|
|
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
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (isExternalTref && isXref) {
|
|
185
|
+
// Build a readable list of source files for the warning message
|
|
186
|
+
const sourceFilesList = existingXtref.sourceFile
|
|
187
|
+
? existingXtref.sourceFile
|
|
188
|
+
: (existingXtref.sourceFiles || []).map(sf => sf.file).join(', ');
|
|
189
|
+
|
|
190
|
+
// Construct the external repository URL
|
|
191
|
+
const externalRepoUrl = existingXtref.ghPageUrl || existingXtref.repoUrl || `https://github.com/${existingXtref.owner}/${existingXtref.repo}`;
|
|
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
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
matchedCount++;
|
|
137
201
|
} else {
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
lastUpdated: new Date().toISOString()
|
|
142
|
-
});
|
|
202
|
+
// Skip terms that are not referenced in the local markdown files
|
|
203
|
+
// This prevents bloating the xtrefs-data.json with unreferenced terms
|
|
204
|
+
skippedCount++;
|
|
143
205
|
}
|
|
144
206
|
});
|
|
145
207
|
|
|
@@ -150,10 +212,14 @@ async function mergeXrefTermsIntoAllXTrefs(xrefTerms, outputPathJSON, outputPath
|
|
|
150
212
|
const stringReadyForFileWrite = `const allXTrefs = ${allXTrefsStr};`;
|
|
151
213
|
fs.writeFileSync(outputPathJS, stringReadyForFileWrite, 'utf8');
|
|
152
214
|
|
|
153
|
-
Logger.success(`Merged ${
|
|
215
|
+
Logger.success(`Merged xref terms: ${matchedCount} matched, ${skippedCount} skipped (not referenced). Total entries: ${allXTrefs.xtrefs.length}`);
|
|
154
216
|
|
|
155
217
|
} catch (error) {
|
|
156
|
-
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
|
+
});
|
|
157
223
|
}
|
|
158
224
|
}
|
|
159
225
|
|
|
@@ -181,18 +247,18 @@ function extractTermsFromHtml(externalSpec, html) {
|
|
|
181
247
|
batch.each((index, termElement) => {
|
|
182
248
|
try {
|
|
183
249
|
const $termElement = $(termElement);
|
|
184
|
-
|
|
250
|
+
|
|
185
251
|
// The id can be on the dt element itself OR on span(s) inside it
|
|
186
252
|
// Some terms have multiple nested spans with different IDs (e.g., aliases)
|
|
187
253
|
// We need to extract ALL term IDs from the dt element
|
|
188
254
|
const termIds = [];
|
|
189
|
-
|
|
255
|
+
|
|
190
256
|
// First check if dt itself has an id
|
|
191
257
|
const dtId = $termElement.attr('id');
|
|
192
258
|
if (dtId && dtId.includes('term:')) {
|
|
193
259
|
termIds.push(dtId.replace('term:', ''));
|
|
194
260
|
}
|
|
195
|
-
|
|
261
|
+
|
|
196
262
|
// Then find all spans with ids that contain 'term:'
|
|
197
263
|
$termElement.find('span[id*="term:"]').each((i, span) => {
|
|
198
264
|
const spanId = $(span).attr('id');
|
|
@@ -203,17 +269,23 @@ function extractTermsFromHtml(externalSpec, html) {
|
|
|
203
269
|
}
|
|
204
270
|
}
|
|
205
271
|
});
|
|
206
|
-
|
|
272
|
+
|
|
207
273
|
// Skip if no valid term IDs found
|
|
208
274
|
if (termIds.length === 0) {
|
|
209
275
|
return;
|
|
210
276
|
}
|
|
211
|
-
|
|
277
|
+
|
|
278
|
+
// Extract classes from the <dt> element to determine if it's a local or external term.
|
|
279
|
+
// This helps identify if a tref to an external resource is itself a tref (term-external).
|
|
280
|
+
const dtClasses = $termElement.attr('class');
|
|
281
|
+
const classArray = dtClasses ? dtClasses.split(/\s+/).filter(Boolean) : [];
|
|
282
|
+
const termClasses = classArray.filter(cls => cls === 'term-local' || cls === 'term-external');
|
|
283
|
+
|
|
212
284
|
const dd = $termElement.next('dd');
|
|
213
285
|
|
|
214
286
|
if (dd.length > 0) {
|
|
215
287
|
const ddContent = $.html(dd); // Store the complete DD content once
|
|
216
|
-
|
|
288
|
+
|
|
217
289
|
// Create a term object for each ID found in this dt element
|
|
218
290
|
// This handles cases where one term definition has multiple aliases
|
|
219
291
|
termIds.forEach(termName => {
|
|
@@ -221,16 +293,21 @@ function extractTermsFromHtml(externalSpec, html) {
|
|
|
221
293
|
externalSpec: externalSpec,
|
|
222
294
|
term: termName,
|
|
223
295
|
content: ddContent,
|
|
296
|
+
classes: termClasses, // CSS classes from dt element (term-local or term-external)
|
|
224
297
|
// Add metadata for consistency with tref structure
|
|
225
298
|
source: 'xref', // Distinguish from tref entries
|
|
226
|
-
termId: `term:${
|
|
299
|
+
termId: `term:${termName}`, // Term ID matches the actual HTML anchor format
|
|
227
300
|
};
|
|
228
301
|
|
|
229
302
|
terms.push(termObj);
|
|
230
303
|
});
|
|
231
304
|
}
|
|
232
305
|
} catch (termError) {
|
|
233
|
-
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
|
+
});
|
|
234
311
|
}
|
|
235
312
|
});
|
|
236
313
|
|
|
@@ -244,7 +321,11 @@ function extractTermsFromHtml(externalSpec, html) {
|
|
|
244
321
|
return terms;
|
|
245
322
|
|
|
246
323
|
} catch (error) {
|
|
247
|
-
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
|
+
});
|
|
248
329
|
return [];
|
|
249
330
|
}
|
|
250
331
|
}
|
|
@@ -12,6 +12,16 @@ const Logger = require('../../utils/logger');
|
|
|
12
12
|
|
|
13
13
|
const CACHE_DIR = getPath('githubcache');
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Retrieves the latest commit hash for a specific file in a GitHub repository.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} token - GitHub API token for authentication
|
|
19
|
+
* @param {string} owner - Repository owner
|
|
20
|
+
* @param {string} repo - Repository name
|
|
21
|
+
* @param {string} filePath - Path to the file within the repository
|
|
22
|
+
* @param {Object} headers - HTTP headers for the request
|
|
23
|
+
* @returns {Promise<string|null>} The commit SHA or null if not found
|
|
24
|
+
*/
|
|
15
25
|
async function getFileCommitHash(token, owner, repo, filePath, headers) {
|
|
16
26
|
try {
|
|
17
27
|
const normalizedPath = filePath.replace(/^\//, '');
|
|
@@ -31,6 +41,27 @@ async function getFileCommitHash(token, owner, repo, filePath, headers) {
|
|
|
31
41
|
}
|
|
32
42
|
}
|
|
33
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Fetches all term definitions from an external specification repository.
|
|
46
|
+
* Extracts terms from the generated index.html file and includes CSS classes
|
|
47
|
+
* to identify whether terms are local definitions or external references.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} token - GitHub API token for authentication
|
|
50
|
+
* @param {string} owner - Repository owner
|
|
51
|
+
* @param {string} repo - Repository name
|
|
52
|
+
* @param {Object} [options={}] - Additional options
|
|
53
|
+
* @param {string} [options.ghPageUrl] - GitHub Pages URL for the repository
|
|
54
|
+
* @returns {Promise<Object|null>} Object containing:
|
|
55
|
+
* - {number} timestamp - Unix timestamp when terms were fetched
|
|
56
|
+
* - {string} repository - Full repository path (owner/repo)
|
|
57
|
+
* - {Array<Object>} terms - Array of term objects, each containing:
|
|
58
|
+
* - {string} term - The term identifier
|
|
59
|
+
* - {string} definition - HTML definition content
|
|
60
|
+
* - {Array<string>} classes - CSS classes from the dt element ('term-local' or 'term-external')
|
|
61
|
+
* - {string|null} sha - Commit hash
|
|
62
|
+
* - {string|null} avatarUrl - Avatar URL
|
|
63
|
+
* - {string} outputFileName - Name of the cached file
|
|
64
|
+
*/
|
|
34
65
|
async function fetchAllTermsFromIndex(token, owner, repo, options = {}) {
|
|
35
66
|
try {
|
|
36
67
|
const headers = token ? { Authorization: `token ${token}` } : {};
|
|
@@ -118,6 +149,12 @@ async function fetchAllTermsFromIndex(token, owner, repo, options = {}) {
|
|
|
118
149
|
return;
|
|
119
150
|
}
|
|
120
151
|
|
|
152
|
+
// Extract classes from the <dt> element to determine if it's a local or external term.
|
|
153
|
+
// This helps identify if a tref to an external resource is itself a tref (term-external)
|
|
154
|
+
// or a local definition (term-local).
|
|
155
|
+
const dtClasses = dt.className ? dt.className.split(/\s+/).filter(Boolean) : [];
|
|
156
|
+
const termClasses = dtClasses.filter(cls => cls === 'term-local' || cls === 'term-external');
|
|
157
|
+
|
|
121
158
|
const definitions = [];
|
|
122
159
|
let pointer = dt.nextElementSibling;
|
|
123
160
|
while (pointer && pointer.tagName.toLowerCase() === 'dd') {
|
|
@@ -125,7 +162,11 @@ async function fetchAllTermsFromIndex(token, owner, repo, options = {}) {
|
|
|
125
162
|
pointer = pointer.nextElementSibling;
|
|
126
163
|
}
|
|
127
164
|
|
|
128
|
-
terms.push({
|
|
165
|
+
terms.push({
|
|
166
|
+
term: termText,
|
|
167
|
+
definition: definitions.join('\n'),
|
|
168
|
+
classes: termClasses
|
|
169
|
+
});
|
|
129
170
|
});
|
|
130
171
|
|
|
131
172
|
const timestamp = Date.now();
|
|
@@ -163,6 +204,25 @@ async function fetchAllTermsFromIndex(token, owner, repo, options = {}) {
|
|
|
163
204
|
}
|
|
164
205
|
}
|
|
165
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Fetches a specific term definition from an external specification repository.
|
|
209
|
+
* This is a convenience wrapper around fetchAllTermsFromIndex that returns
|
|
210
|
+
* only a single matching term.
|
|
211
|
+
*
|
|
212
|
+
* @param {string} token - GitHub API token for authentication
|
|
213
|
+
* @param {string} term - The specific term to fetch
|
|
214
|
+
* @param {string} owner - Repository owner
|
|
215
|
+
* @param {string} repo - Repository name
|
|
216
|
+
* @param {string} termsDir - Terms directory (currently unused but kept for compatibility)
|
|
217
|
+
* @param {Object} [options={}] - Additional options
|
|
218
|
+
* @param {string} [options.ghPageUrl] - GitHub Pages URL for the repository
|
|
219
|
+
* @returns {Promise<Object|null>} Object containing:
|
|
220
|
+
* - {string} term - The term identifier
|
|
221
|
+
* - {string} content - HTML definition content
|
|
222
|
+
* - {Array<string>} classes - CSS classes ('term-local' or 'term-external')
|
|
223
|
+
* - {string|null} sha - Commit hash
|
|
224
|
+
* - {Object} repository - Repository metadata
|
|
225
|
+
*/
|
|
166
226
|
async function fetchTermsFromIndex(token, term, owner, repo, termsDir, options = {}) {
|
|
167
227
|
const allTermsData = await fetchAllTermsFromIndex(token, owner, repo, options);
|
|
168
228
|
if (!allTermsData || !Array.isArray(allTermsData.terms)) {
|
|
@@ -179,6 +239,7 @@ async function fetchTermsFromIndex(token, term, owner, repo, termsDir, options =
|
|
|
179
239
|
return {
|
|
180
240
|
term: foundTerm.term,
|
|
181
241
|
content: foundTerm.definition,
|
|
242
|
+
classes: foundTerm.classes || [],
|
|
182
243
|
sha: allTermsData.sha,
|
|
183
244
|
repository: {
|
|
184
245
|
owner: {
|
|
@@ -14,7 +14,11 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
|
|
|
14
14
|
|
|
15
15
|
allXTrefs.xtrefs = allXTrefs.xtrefs.filter(xtref => {
|
|
16
16
|
if (!xtref.owner || !xtref.repo || !xtref.repoUrl) {
|
|
17
|
-
Logger.error(`Removing incomplete reference: ${xtref.externalSpec}, ${xtref.term}
|
|
17
|
+
Logger.error(`Removing incomplete reference: ${xtref.externalSpec}, ${xtref.term}`, {
|
|
18
|
+
context: 'External reference is missing required repository information',
|
|
19
|
+
hint: 'Verify external_specs configuration in specs.json includes github_repo_url for each entry',
|
|
20
|
+
details: `Missing: ${!xtref.owner ? 'owner' : ''} ${!xtref.repo ? 'repo' : ''} ${!xtref.repoUrl ? 'repoUrl' : ''}`
|
|
21
|
+
});
|
|
18
22
|
return false;
|
|
19
23
|
}
|
|
20
24
|
return true;
|
|
@@ -51,7 +55,11 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
|
|
|
51
55
|
);
|
|
52
56
|
|
|
53
57
|
if (!allTermsData) {
|
|
54
|
-
Logger.error(`Could not fetch terms from repository ${repoKey}
|
|
58
|
+
Logger.error(`Could not fetch terms from repository ${repoKey}`, {
|
|
59
|
+
context: `Failed to retrieve terminology from ${repoUrl}`,
|
|
60
|
+
hint: 'Ensure the repository exists, is accessible, and has published its spec. If it\'s private, set GITHUB_PAT environment variable',
|
|
61
|
+
details: `Repository: ${repoUrl}. Check if GitHub Pages is enabled or if the repo has a valid specs.json`
|
|
62
|
+
});
|
|
55
63
|
repoGroup.xtrefs.forEach(xtref => {
|
|
56
64
|
xtref.commitHash = 'not found';
|
|
57
65
|
xtref.content = 'This term was not found in the external repository.';
|
|
@@ -69,12 +77,54 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
|
|
|
69
77
|
xtref.commitHash = allTermsData.sha;
|
|
70
78
|
xtref.content = foundTerm.definition;
|
|
71
79
|
xtref.avatarUrl = allTermsData.avatarUrl;
|
|
80
|
+
// Copy the classes array from the foundTerm to identify if this is a local or external term.
|
|
81
|
+
// This helps determine if a tref to an external resource is itself a tref (term-external).
|
|
82
|
+
xtref.classes = foundTerm.classes || [];
|
|
83
|
+
|
|
84
|
+
// Check if this is a tref to an external tref (nested tref)
|
|
85
|
+
// A term with 'term-external' class means it's transcluded from another spec
|
|
86
|
+
const isExternalTref = foundTerm.classes && foundTerm.classes.includes('term-external');
|
|
87
|
+
const isTref = xtref.sourceFiles && xtref.sourceFiles.some(sf => sf.type === 'tref');
|
|
88
|
+
const isXref = xtref.sourceFiles && xtref.sourceFiles.some(sf => sf.type === 'xref');
|
|
89
|
+
|
|
90
|
+
if (isExternalTref && isTref) {
|
|
91
|
+
// Build a readable list of source files for the error message
|
|
92
|
+
const sourceFilesList = xtref.sourceFile
|
|
93
|
+
? xtref.sourceFile
|
|
94
|
+
: (xtref.sourceFiles || []).map(sf => sf.file).join(', ');
|
|
95
|
+
|
|
96
|
+
// Construct the external repository URL
|
|
97
|
+
const externalRepoUrl = xtref.ghPageUrl || xtref.repoUrl || `https://github.com/${xtref.owner}/${xtref.repo}`;
|
|
98
|
+
|
|
99
|
+
Logger.error(`NESTED TREF DETECTED: Term "${xtref.term}" in ${xtref.externalSpec}`, {
|
|
100
|
+
context: `Origin: ${sourceFilesList} - This term is itself a tref transcluded from another spec`,
|
|
101
|
+
hint: 'Avoid chaining trefs (tref → tref). Either reference the original source spec directly, or define the term locally',
|
|
102
|
+
details: `Repository: ${externalRepoUrl}. Nested trefs create complex dependency chains`
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (isExternalTref && isXref) {
|
|
107
|
+
// Build a readable list of source files for the warning message
|
|
108
|
+
const sourceFilesList = xtref.sourceFile
|
|
109
|
+
? xtref.sourceFile
|
|
110
|
+
: (xtref.sourceFiles || []).map(sf => sf.file).join(', ');
|
|
111
|
+
|
|
112
|
+
// Construct the external repository URL
|
|
113
|
+
const externalRepoUrl = xtref.ghPageUrl || xtref.repoUrl || `https://github.com/${xtref.owner}/${xtref.repo}`;
|
|
114
|
+
|
|
115
|
+
Logger.error(`NESTED XREF DETECTED: Term "${xtref.term}" in ${xtref.externalSpec}`, {
|
|
116
|
+
context: `Origin: ${sourceFilesList} - This xref points to a term that is already transcluded elsewhere`,
|
|
117
|
+
hint: 'Use [[xref]] only for terms directly defined in the external spec. For nested refs, reference the original source',
|
|
118
|
+
details: `Repository: ${externalRepoUrl}. This creates a chain of external references`
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
72
122
|
Logger.success(`Match found for term: ${xtref.term} in ${xtref.externalSpec}`);
|
|
73
123
|
} else {
|
|
74
124
|
xtref.commitHash = 'not found';
|
|
75
125
|
xtref.content = 'This term was not found in the external repository.';
|
|
76
126
|
xtref.avatarUrl = null;
|
|
77
|
-
|
|
127
|
+
|
|
78
128
|
// Build a readable list of source files for the error message.
|
|
79
129
|
// Two possible data structures exist:
|
|
80
130
|
// 1. xtref.sourceFile is a STRING like "primitive.md"
|
|
@@ -85,15 +135,19 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
|
|
|
85
135
|
// - Otherwise → extract file names from the sourceFiles array:
|
|
86
136
|
// - .map(sf => sf.file) extracts just the filename from each object
|
|
87
137
|
// - .join(', ') combines them into a comma-separated string
|
|
88
|
-
const sourceFilesList = xtref.sourceFile
|
|
89
|
-
? xtref.sourceFile
|
|
138
|
+
const sourceFilesList = xtref.sourceFile
|
|
139
|
+
? xtref.sourceFile
|
|
90
140
|
: (xtref.sourceFiles || []).map(sf => sf.file).join(', ');
|
|
91
141
|
|
|
92
142
|
// Prefer an explicit repo URL if provided on the xtref, otherwise
|
|
93
143
|
// build a standard GitHub URL from the owner/repo.
|
|
94
144
|
const githubUrl = xtref.repoUrl || `https://github.com/${repoKey}`;
|
|
95
145
|
|
|
96
|
-
Logger.error(`
|
|
146
|
+
Logger.error(`No match found for term: ${xtref.term} in ${xtref.externalSpec}`, {
|
|
147
|
+
context: `Origin: ${sourceFilesList} - Term not found in external repository`,
|
|
148
|
+
hint: 'Check if the term exists in the external spec. Verify spelling, ensure the external spec has published, and confirm the term is in their terminology section',
|
|
149
|
+
details: `Repository: ${githubUrl}. The term may have been renamed or removed`
|
|
150
|
+
});
|
|
97
151
|
}
|
|
98
152
|
}
|
|
99
153
|
|
|
@@ -104,10 +158,14 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
|
|
|
104
158
|
const allXTrefsStr = JSON.stringify(allXTrefs, null, 2);
|
|
105
159
|
fs.writeFileSync(outputPathJSON, allXTrefsStr, 'utf8');
|
|
106
160
|
const jsPayload = `const allXTrefs = ${allXTrefsStr};`;
|
|
107
|
-
|
|
108
|
-
|
|
161
|
+
fs.writeFileSync(outputPathJS, jsPayload, 'utf8');
|
|
162
|
+
fs.writeFileSync(outputPathJSTimeStamped, jsPayload, 'utf8');
|
|
109
163
|
} catch (error) {
|
|
110
|
-
Logger.error('An error occurred
|
|
164
|
+
Logger.error('An error occurred during xtrefs processing', {
|
|
165
|
+
context: 'Failed while processing external references and fetching terms',
|
|
166
|
+
hint: 'Check your internet connection, verify GITHUB_PAT is set if needed, and ensure specs.json external_specs configuration is correct',
|
|
167
|
+
details: error.message
|
|
168
|
+
});
|
|
111
169
|
}
|
|
112
170
|
}
|
|
113
171
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const { externalReferences, utils } = require('../../utils/regex-patterns');
|
|
10
|
+
const Logger = require('../../utils/logger');
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Checks if a specific xtref is present in the markdown content.
|
|
@@ -43,13 +44,30 @@ function isXTrefInAnyFile(xtref, fileContents) {
|
|
|
43
44
|
* @param {object} xtrefObject - Pre-parsed xtref object from template-tag-parser
|
|
44
45
|
* @param {{ xtrefs: Array<object> }} allXTrefs - Aggregated reference collection
|
|
45
46
|
* @param {string|null} filename - Originating filename for bookkeeping
|
|
47
|
+
* @param {Array<object>|null} externalSpecs - Array of external_specs from specs.json for validation
|
|
46
48
|
* @returns {{ xtrefs: Array<object> }} Updated reference collection
|
|
47
49
|
*/
|
|
48
|
-
function addXtrefToCollection(xtrefObject, allXTrefs, filename = null) {
|
|
50
|
+
function addXtrefToCollection(xtrefObject, allXTrefs, filename = null, externalSpecs = null) {
|
|
49
51
|
const referenceType = xtrefObject.referenceType;
|
|
50
52
|
const cleanXTrefObj = { ...xtrefObject };
|
|
51
53
|
delete cleanXTrefObj.referenceType;
|
|
52
54
|
|
|
55
|
+
// Validate that the external spec exists in specs.json configuration
|
|
56
|
+
if (externalSpecs && Array.isArray(externalSpecs)) {
|
|
57
|
+
const externalSpecExists = externalSpecs.some(
|
|
58
|
+
spec => spec.external_spec === cleanXTrefObj.externalSpec
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (!externalSpecExists) {
|
|
62
|
+
const availableSpecs = externalSpecs.map(s => s.external_spec).join(', ');
|
|
63
|
+
Logger.error(
|
|
64
|
+
`External spec "${cleanXTrefObj.externalSpec}" not found in specs.json configuration. ` +
|
|
65
|
+
`Available external specs: ${availableSpecs}. ` +
|
|
66
|
+
`Check [[${referenceType}: ${cleanXTrefObj.externalSpec}, ${cleanXTrefObj.term}]] in ${filename || 'unknown file'}`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
53
71
|
const existingIndex = allXTrefs?.xtrefs?.findIndex(existingXTref =>
|
|
54
72
|
existingXTref.term === cleanXTrefObj.term &&
|
|
55
73
|
existingXTref.externalSpec === cleanXTrefObj.externalSpec
|
|
@@ -135,9 +153,10 @@ function addXtrefToCollection(xtrefObject, allXTrefs, filename = null) {
|
|
|
135
153
|
* @param {{ xtrefs: Array<object> }} allXTrefs - Aggregated reference collection.
|
|
136
154
|
* @param {string|null} filename - Originating filename for bookkeeping.
|
|
137
155
|
* @param {function} processXTrefObject - Parsing function for xtref strings.
|
|
156
|
+
* @param {Array<object>|null} externalSpecs - Array of external_specs from specs.json for validation.
|
|
138
157
|
* @returns {{ xtrefs: Array<object> }} Updated reference collection.
|
|
139
158
|
*/
|
|
140
|
-
function addNewXTrefsFromMarkdown(markdownContent, allXTrefs, filename = null, processXTrefObject) {
|
|
159
|
+
function addNewXTrefsFromMarkdown(markdownContent, allXTrefs, filename = null, processXTrefObject, externalSpecs = null) {
|
|
141
160
|
if (!processXTrefObject) {
|
|
142
161
|
throw new Error('processXTrefObject function is required. Import from template-tag-parser.');
|
|
143
162
|
}
|
|
@@ -152,7 +171,7 @@ function addNewXTrefsFromMarkdown(markdownContent, allXTrefs, filename = null, p
|
|
|
152
171
|
|
|
153
172
|
xtrefs.forEach(rawXtref => {
|
|
154
173
|
const xtrefObject = processXTrefObject(rawXtref);
|
|
155
|
-
addXtrefToCollection(xtrefObject, allXTrefs, filename);
|
|
174
|
+
addXtrefToCollection(xtrefObject, allXTrefs, filename, externalSpecs);
|
|
156
175
|
});
|
|
157
176
|
|
|
158
177
|
return allXTrefs;
|
|
@@ -111,7 +111,6 @@ async function render(spec, assets, sharedVars, config, template, assetsGlobal,
|
|
|
111
111
|
assetsHead: assets.head,
|
|
112
112
|
assetsBody: assets.body,
|
|
113
113
|
assetsSvg: assets.svg,
|
|
114
|
-
features: Object.keys(features).join(' '),
|
|
115
114
|
externalReferences: '', // No longer inject DOM HTML - xrefs are in allXTrefs
|
|
116
115
|
xtrefsData: createScriptElementWithXTrefDataForEmbeddingInHtml(),
|
|
117
116
|
specLogo: spec.logo,
|