spec-up-t 1.1.54 → 1.2.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 +59 -8
- package/assets/compiled/head.css +13 -12
- package/assets/css/adjust-font-size.css +11 -0
- package/assets/css/collapse-meta-info.css +27 -8
- package/assets/css/create-term-filter.css +11 -0
- package/assets/css/index.css +15 -0
- package/assets/css/prism.css +176 -153
- package/assets/css/prism.dark.css +157 -0
- package/assets/css/prism.default.css +180 -0
- package/assets/css/terms-and-definitions.1.css +223 -0
- package/assets/css/terms-and-definitions.css +214 -100
- package/assets/js/addAnchorsToTerms.js +1 -1
- package/assets/js/collapse-definitions.js +2 -1
- package/assets/js/collapse-meta-info.js +25 -11
- package/assets/js/create-term-filter.js +61 -0
- package/assets/js/horizontal-scroll-hint.js +159 -0
- package/assets/js/index.1.js +137 -0
- package/assets/js/index.js +2 -1
- package/assets/js/insert-trefs.js +122 -116
- package/assets/js/insert-xrefs.1.js +372 -0
- package/assets/js/{show-commit-hashes.js → insert-xrefs.js} +67 -7
- package/assets/js/prism.dark.js +24 -0
- package/assets/js/prism.default.js +23 -0
- package/assets/js/prism.js +4 -5
- package/assets/js/search.js +1 -1
- package/assets/js/toggle-dense-info.js +40 -0
- package/branches.md +4 -29
- package/index.js +397 -189
- package/index.new.js +662 -0
- package/package.json +1 -1
- package/src/asset-map.json +9 -5
- package/src/collect-external-references.js +16 -9
- package/src/collectExternalReferences/fetchTermsFromIndex.js +328 -0
- package/src/collectExternalReferences/processXTrefsData.js +73 -18
- package/src/create-pdf.js +385 -89
- package/src/health-check/external-specs-checker.js +207 -0
- package/src/health-check/output-gitignore-checker.js +261 -0
- package/src/health-check/specs-configuration-checker.js +274 -0
- package/src/health-check/term-references-checker.js +191 -0
- package/src/health-check/terms-intro-checker.js +81 -0
- package/src/health-check/tref-term-checker.js +463 -0
- package/src/health-check.js +445 -0
- package/src/install-from-boilerplate/config-scripts-keys.js +2 -0
- package/src/install-from-boilerplate/menu.sh +6 -3
- package/src/markdown-it-extensions.js +134 -103
- package/src/prepare-tref.js +61 -24
- package/src/utils/fetch.js +100 -0
- package/templates/template.html +12 -7
- package/assets/js/css-helper.js +0 -30
- package/src/collectExternalReferences/fetchTermsFromGitHubRepository.js +0 -232
- package/src/collectExternalReferences/fetchTermsFromGitHubRepository.test.js +0 -385
- package/src/collectExternalReferences/octokitClient.js +0 -96
package/src/asset-map.json
CHANGED
|
@@ -2,23 +2,24 @@
|
|
|
2
2
|
"head": {
|
|
3
3
|
"css": [
|
|
4
4
|
"assets/css/theme-vars.css",
|
|
5
|
+
"assets/css/bootstrap.min.css",
|
|
5
6
|
"assets/css/custom-elements.css",
|
|
6
7
|
"assets/css/prism.css",
|
|
7
8
|
"assets/css/chart.css",
|
|
9
|
+
"assets/css/header-navbar.css",
|
|
10
|
+
"assets/css/sidebar-toc.css",
|
|
11
|
+
"assets/css/terms-and-definitions.css",
|
|
8
12
|
"assets/css/edit-term-buttons.css",
|
|
9
13
|
"assets/css/search.css",
|
|
10
14
|
"assets/css/highlightMenuItems.css",
|
|
11
15
|
"assets/css/backToTop.css",
|
|
12
16
|
"assets/css/notyf.css",
|
|
13
17
|
"assets/css/collapse-definitions.css",
|
|
18
|
+
"assets/css/create-term-filter.css",
|
|
14
19
|
"assets/css/collapse-meta-info.css",
|
|
15
20
|
"assets/css/modal.css",
|
|
16
21
|
"assets/css/create-alphabet-index.css",
|
|
17
22
|
"assets/css/pdf-download.css",
|
|
18
|
-
"assets/css/terms-and-definitions.css",
|
|
19
|
-
"assets/css/bootstrap.min.css",
|
|
20
|
-
"assets/css/header-navbar.css",
|
|
21
|
-
"assets/css/sidebar-toc.css",
|
|
22
23
|
"assets/css/loader.css",
|
|
23
24
|
"assets/css/external-links.css",
|
|
24
25
|
"assets/css/repo-issues.css",
|
|
@@ -56,12 +57,15 @@
|
|
|
56
57
|
"assets/js/pdf-download.js",
|
|
57
58
|
"assets/js/insert-trefs.js",
|
|
58
59
|
"assets/js/collapse-definitions.js",
|
|
60
|
+
"assets/js/create-term-filter.js",
|
|
59
61
|
"assets/js/collapse-meta-info.js",
|
|
60
62
|
"assets/js/add-href-to-snapshot-link.js",
|
|
61
63
|
"assets/js/adjust-font-size.js",
|
|
64
|
+
"assets/js/toggle-dense-info.js",
|
|
62
65
|
"assets/js/close-off-canvas-menu.js",
|
|
66
|
+
|
|
63
67
|
"assets/js/index.js",
|
|
64
|
-
"assets/js/
|
|
68
|
+
"assets/js/horizontal-scroll-hint.js",
|
|
65
69
|
"assets/js/bootstrap.bundle.min.js"
|
|
66
70
|
]
|
|
67
71
|
}
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
const { shouldProcessFile } = require('./utils/file-filter');
|
|
45
|
+
const path = require('path');
|
|
46
|
+
|
|
45
47
|
function isXTrefInMarkdown(xtref, markdownContent) {
|
|
46
48
|
const regex = new RegExp(`\\[\\[(?:x|t)ref:${xtref.externalSpec},\\s*${xtref.term}\\]\\]`, 'g');
|
|
47
49
|
return regex.test(markdownContent);
|
|
@@ -192,22 +194,26 @@ function collectExternalReferences(options = {}) {
|
|
|
192
194
|
// Collect all directories that contain files with a term and definition
|
|
193
195
|
// This maps over the specs in the config file and constructs paths to directories
|
|
194
196
|
// where the term definition files are located.
|
|
195
|
-
const specTermsDirectories = config.specs.map(spec =>
|
|
197
|
+
const specTermsDirectories = config.specs.map(spec =>
|
|
198
|
+
path.join(spec.spec_directory, spec.spec_terms_directory)
|
|
199
|
+
);
|
|
196
200
|
|
|
197
201
|
// Ensure that the 'output' directory exists, creating it if necessary.
|
|
198
|
-
|
|
199
|
-
|
|
202
|
+
const outputDir = 'output';
|
|
203
|
+
if (!fs.existsSync(outputDir)) {
|
|
204
|
+
fs.mkdirSync(outputDir);
|
|
200
205
|
}
|
|
201
206
|
|
|
202
207
|
// Ensure that the 'output/xtrefs-history' directory exists, creating it if necessary.
|
|
203
|
-
|
|
204
|
-
|
|
208
|
+
const xtrefsHistoryDir = path.join(outputDir, 'xtrefs-history');
|
|
209
|
+
if (!fs.existsSync(xtrefsHistoryDir)) {
|
|
210
|
+
fs.mkdirSync(xtrefsHistoryDir);
|
|
205
211
|
}
|
|
206
212
|
|
|
207
213
|
// Define paths for various output files, including JSON and JS files.
|
|
208
|
-
const outputPathJSON = '
|
|
209
|
-
const outputPathJS = '
|
|
210
|
-
const outputPathJSTimeStamped =
|
|
214
|
+
const outputPathJSON = path.join(outputDir, 'xtrefs-data.json');
|
|
215
|
+
const outputPathJS = path.join(outputDir, 'xtrefs-data.js');
|
|
216
|
+
const outputPathJSTimeStamped = path.join(xtrefsHistoryDir, `xtrefs-data-${Date.now()}.js`);
|
|
211
217
|
|
|
212
218
|
// Function to extend xtref objects with additional information, such as repository URL and directory information.
|
|
213
219
|
function extendXTrefs(config, xtrefs) {
|
|
@@ -280,7 +286,8 @@ function collectExternalReferences(options = {}) {
|
|
|
280
286
|
specTermsDirectories.forEach(specDirectory => {
|
|
281
287
|
fs.readdirSync(specDirectory).forEach(file => {
|
|
282
288
|
if (shouldProcessFile(file)) {
|
|
283
|
-
const
|
|
289
|
+
const filePath = path.join(specDirectory, file);
|
|
290
|
+
const markdown = fs.readFileSync(filePath, 'utf8');
|
|
284
291
|
allMarkdownContent += markdown;
|
|
285
292
|
}
|
|
286
293
|
});
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file fetchTermsFromIndex.js
|
|
3
|
+
* @description Fetches terms and definitions from external repository's index.html
|
|
4
|
+
* @author Generated with assistance from GitHub Copilot
|
|
5
|
+
* @version 1.0.0
|
|
6
|
+
* @since 2025-04-15
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { JSDOM } = require('jsdom');
|
|
12
|
+
const axios = require('axios');
|
|
13
|
+
const { addPath, getPath, getAllPaths } = require('../config/paths');
|
|
14
|
+
const crypto = require('crypto');
|
|
15
|
+
|
|
16
|
+
// Directory to store cached files
|
|
17
|
+
const CACHE_DIR = getPath('githubcache');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generates a cache key based on repository information
|
|
21
|
+
* @param {string} owner - Repository owner
|
|
22
|
+
* @param {string} repo - Repository name
|
|
23
|
+
* @returns {string} - Cache key
|
|
24
|
+
*/
|
|
25
|
+
function generateCacheKey(owner, repo) {
|
|
26
|
+
const input = `${owner}-${repo}-index`;
|
|
27
|
+
return crypto.createHash('md5').update(input).digest('hex');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Checks if a cached version exists and is valid
|
|
32
|
+
* @param {string} cacheKey - Cache key
|
|
33
|
+
* @param {object} options - Options object
|
|
34
|
+
* @param {number} options.cacheTTL - Time-to-live for cache in milliseconds (default: 24 hours)
|
|
35
|
+
* @returns {object|null} - Cached data or null if not found or expired
|
|
36
|
+
*/
|
|
37
|
+
function getFromCache(cacheKey, options = {}) {
|
|
38
|
+
const cachePath = path.join(CACHE_DIR, `${cacheKey}.json`);
|
|
39
|
+
// const cacheTTL = options.cacheTTL || 24 * 60 * 60 * 1000; // Default: 24 hours
|
|
40
|
+
const cacheTTL = 0;
|
|
41
|
+
|
|
42
|
+
if (!fs.existsSync(cachePath)) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const cacheData = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
|
|
47
|
+
const cacheTime = new Date(cacheData.timestamp).getTime();
|
|
48
|
+
const currentTime = new Date().getTime();
|
|
49
|
+
|
|
50
|
+
// Check if cache is expired
|
|
51
|
+
if (currentTime - cacheTime > cacheTTL) {
|
|
52
|
+
console.log(`Cache expired for key: ${cacheKey}`);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log(`Using cached data for key: ${cacheKey}`);
|
|
57
|
+
return cacheData;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Saves data to cache
|
|
62
|
+
* @param {string} cacheKey - Cache key
|
|
63
|
+
* @param {object} data - Data to cache
|
|
64
|
+
*/
|
|
65
|
+
function saveToCache(cacheKey, data) {
|
|
66
|
+
const cachePath = path.join(CACHE_DIR, `${cacheKey}.json`);
|
|
67
|
+
const cacheData = {
|
|
68
|
+
timestamp: new Date().toISOString(),
|
|
69
|
+
...data
|
|
70
|
+
};
|
|
71
|
+
fs.writeFileSync(cachePath, JSON.stringify(cacheData, null, 2));
|
|
72
|
+
console.log(`Saved to cache: ${cacheKey}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Fetches the latest commit hash for a specific file in a repository
|
|
77
|
+
* @param {string} token - GitHub API Token
|
|
78
|
+
* @param {string} owner - Repository owner
|
|
79
|
+
* @param {string} repo - Repository name
|
|
80
|
+
* @param {string} filePath - Path to the file in the repository
|
|
81
|
+
* @param {object} headers - Request headers
|
|
82
|
+
* @returns {string|null} - Latest commit hash or null if error
|
|
83
|
+
*/
|
|
84
|
+
async function getFileCommitHash(token, owner, repo, filePath, headers) {
|
|
85
|
+
try {
|
|
86
|
+
// Normalize the file path to ensure it doesn't have leading slash
|
|
87
|
+
const normalizedPath = filePath.replace(/^\//, '');
|
|
88
|
+
|
|
89
|
+
// Construct API URL to get commits for the specific file
|
|
90
|
+
const commitsUrl = `https://api.github.com/repos/${owner}/${repo}/commits?path=${normalizedPath}&per_page=1`;
|
|
91
|
+
console.log(`Fetching latest commit for file: ${commitsUrl}`);
|
|
92
|
+
|
|
93
|
+
const response = await axios.get(commitsUrl, { headers });
|
|
94
|
+
|
|
95
|
+
if (response.status !== 200 || !response.data || response.data.length === 0) {
|
|
96
|
+
console.log(`❌ Could not find commit information for ${filePath}`);
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Return the SHA of the latest commit
|
|
101
|
+
return response.data[0].sha;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error(`❌ Error fetching commit hash: ${error.message}`);
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Fetches all terms and definitions from a repository's index.html
|
|
110
|
+
* @param {string} token - GitHub API Token
|
|
111
|
+
* @param {string} owner - Repository owner
|
|
112
|
+
* @param {string} repo - Repository name
|
|
113
|
+
* @param {object} options - Additional options
|
|
114
|
+
* @returns {object|null} - Object containing all terms or null if error
|
|
115
|
+
*/
|
|
116
|
+
async function fetchAllTermsFromIndex(token, owner, repo, options = {}) {
|
|
117
|
+
try {
|
|
118
|
+
// Generate cache key based on repo information
|
|
119
|
+
const cacheKey = generateCacheKey(owner, repo);
|
|
120
|
+
let cachedData = null;
|
|
121
|
+
|
|
122
|
+
// Check cache first if caching is enabled
|
|
123
|
+
if (options.cache !== false) {
|
|
124
|
+
cachedData = getFromCache(cacheKey, options);
|
|
125
|
+
if (cachedData) {
|
|
126
|
+
return cachedData;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Configure headers for GitHub API
|
|
131
|
+
const headers = {};
|
|
132
|
+
if (token) {
|
|
133
|
+
headers['Authorization'] = `token ${token}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Get the specs.json content from the repository to find the output_path
|
|
137
|
+
const specsJsonUrl = `https://api.github.com/repos/${owner}/${repo}/contents/specs.json`;
|
|
138
|
+
console.log(`Fetching specs.json from: ${specsJsonUrl}`);
|
|
139
|
+
|
|
140
|
+
// Fetch specs.json content
|
|
141
|
+
const specsJsonResponse = await axios.get(specsJsonUrl, { headers });
|
|
142
|
+
if (specsJsonResponse.status !== 200) {
|
|
143
|
+
console.log(`❌ Could not find specs.json in repository ${owner}/${repo}`);
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Decode specs.json content from base64
|
|
148
|
+
const specsJsonContent = Buffer.from(specsJsonResponse.data.content, 'base64').toString('utf8');
|
|
149
|
+
const specsJson = JSON.parse(specsJsonContent);
|
|
150
|
+
|
|
151
|
+
// Get the output_path from specs.json
|
|
152
|
+
const outputPath = specsJson.specs[0].output_path;
|
|
153
|
+
if (!outputPath) {
|
|
154
|
+
console.log(`❌ No output_path found in specs.json for repository ${owner}/${repo}`);
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Fix: Properly normalize the output path to ensure it doesn't have leading "./" or trailing "/"
|
|
159
|
+
const normalizedOutputPath = outputPath.replace(/^\.\//, '').replace(/\/$/, '');
|
|
160
|
+
|
|
161
|
+
// Create the path to the index.html file
|
|
162
|
+
const indexHtmlPath = `${normalizedOutputPath}/index.html`;
|
|
163
|
+
|
|
164
|
+
// Fetch the index.html content with properly constructed URL
|
|
165
|
+
const indexHtmlUrl = `https://raw.githubusercontent.com/${owner}/${repo}/main/${indexHtmlPath}`;
|
|
166
|
+
console.log(`Fetching index.html from: ${indexHtmlUrl}`);
|
|
167
|
+
|
|
168
|
+
const indexHtmlResponse = await axios.get(indexHtmlUrl, { headers });
|
|
169
|
+
if (indexHtmlResponse.status !== 200) {
|
|
170
|
+
console.log(`❌ Could not find index.html at ${indexHtmlUrl}`);
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Get the commit hash for the index.html file
|
|
175
|
+
const commitHash = await getFileCommitHash(token, owner, repo, indexHtmlPath, headers);
|
|
176
|
+
if (!commitHash) {
|
|
177
|
+
console.log(`⚠️ Could not get commit hash for index.html, continuing without it`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const htmlContent = indexHtmlResponse.data;
|
|
181
|
+
|
|
182
|
+
// Parse HTML using JSDOM
|
|
183
|
+
const dom = new JSDOM(htmlContent);
|
|
184
|
+
const document = dom.window.document;
|
|
185
|
+
|
|
186
|
+
// Find all term definition lists with class "terms-and-definitions-list"
|
|
187
|
+
const termDlList = document.querySelector('dl.terms-and-definitions-list');
|
|
188
|
+
if (!termDlList) {
|
|
189
|
+
console.log(`❌ No terms-and-definitions-list found in ${indexHtmlUrl}`);
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Extract all terms and definitions
|
|
194
|
+
const terms = [];
|
|
195
|
+
let dtElements = termDlList.querySelectorAll('dt');
|
|
196
|
+
|
|
197
|
+
dtElements.forEach(dt => {
|
|
198
|
+
// Find the term span that starts with id="term:
|
|
199
|
+
const termSpan = dt.querySelector('span[id^="term:"]');
|
|
200
|
+
if (!termSpan) return;
|
|
201
|
+
|
|
202
|
+
// Get the term text (all text content, excluding nested spans)
|
|
203
|
+
let termText = '';
|
|
204
|
+
for (let node of termSpan.childNodes) {
|
|
205
|
+
if (node.nodeType === dom.window.Node.TEXT_NODE) {
|
|
206
|
+
termText += node.textContent.trim();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// If no text found, try to get the full text content
|
|
211
|
+
if (!termText) {
|
|
212
|
+
termText = termSpan.textContent.trim();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Skip empty terms
|
|
216
|
+
if (!termText) return;
|
|
217
|
+
|
|
218
|
+
// Find all corresponding definition elements
|
|
219
|
+
let dds = [];
|
|
220
|
+
let nextElement = dt.nextElementSibling;
|
|
221
|
+
|
|
222
|
+
// Collect all consecutive <dd> elements until we reach another <dt>
|
|
223
|
+
while (nextElement && nextElement.tagName.toLowerCase() === 'dd') {
|
|
224
|
+
dds.push(nextElement.outerHTML);
|
|
225
|
+
nextElement = nextElement.nextElementSibling;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
terms.push({
|
|
229
|
+
term: termText,
|
|
230
|
+
definition: dds.join('\n')
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Store all terms in a JSON file with timestamp
|
|
235
|
+
const timestamp = Date.now();
|
|
236
|
+
const outputDir = path.join(CACHE_DIR);
|
|
237
|
+
if (!fs.existsSync(outputDir)) {
|
|
238
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Create output filename with timestamp
|
|
242
|
+
const outputFileName = `${timestamp}-${owner}-${repo}-terms.json`;
|
|
243
|
+
const outputFilePath = path.join(outputDir, outputFileName);
|
|
244
|
+
|
|
245
|
+
// Create the result object
|
|
246
|
+
const result = {
|
|
247
|
+
timestamp,
|
|
248
|
+
repository: `${owner}/${repo}`,
|
|
249
|
+
terms,
|
|
250
|
+
sha: commitHash, // Use the commit hash of the index.html file
|
|
251
|
+
avatarUrl: null,
|
|
252
|
+
outputFileName
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// Save all terms to file
|
|
256
|
+
fs.writeFileSync(outputFilePath, JSON.stringify(result, null, 2));
|
|
257
|
+
console.log(`✅ Saved ${terms.length} terms to ${outputFilePath}`);
|
|
258
|
+
|
|
259
|
+
// Save to cache if enabled
|
|
260
|
+
if (options.cache !== false) {
|
|
261
|
+
saveToCache(cacheKey, result);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return result;
|
|
265
|
+
|
|
266
|
+
} catch (error) {
|
|
267
|
+
if (error.response) {
|
|
268
|
+
if (error.response.status === 404) {
|
|
269
|
+
console.log(`❌ Resource not found: ${error.config.url}`);
|
|
270
|
+
} else if (error.response.status === 403 && error.response.headers['x-ratelimit-remaining'] === '0') {
|
|
271
|
+
const resetTime = new Date(parseInt(error.response.headers['x-ratelimit-reset']) * 1000);
|
|
272
|
+
console.error(`❌ GitHub API rate limit exceeded. Try again after ${resetTime.toLocaleString()}`);
|
|
273
|
+
} else {
|
|
274
|
+
console.error(`❌ Error fetching data: ${error.response.status} ${error.response.statusText}`);
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
console.error(`❌ Error fetching term: ${error.message}`);
|
|
278
|
+
}
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Fetches a specific term from repository's index.html
|
|
285
|
+
* This is a wrapper that uses fetchAllTermsFromIndex for efficiency
|
|
286
|
+
* @param {string} token - GitHub API Token
|
|
287
|
+
* @param {string} term - The specific term to look for
|
|
288
|
+
* @param {string} owner - Repository owner
|
|
289
|
+
* @param {string} repo - Repository name
|
|
290
|
+
* @param {string} termsDir - Directory containing term definitions (not used in this implementation)
|
|
291
|
+
* @param {object} options - Additional options
|
|
292
|
+
* @returns {object|null} - Found term data or null if not found
|
|
293
|
+
*/
|
|
294
|
+
async function fetchTermsFromIndex(token, term, owner, repo, termsDir, options = {}) {
|
|
295
|
+
// First get all terms from the repository (which is cached)
|
|
296
|
+
const allTermsData = await fetchAllTermsFromIndex(token, owner, repo, options);
|
|
297
|
+
|
|
298
|
+
if (!allTermsData || !allTermsData.terms) {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Find the specific term
|
|
303
|
+
const foundTerm = allTermsData.terms.find(t => t.term.toLowerCase() === term.toLowerCase());
|
|
304
|
+
|
|
305
|
+
if (foundTerm) {
|
|
306
|
+
console.log(`Found term '${term}' in repository ${owner}/${repo}`);
|
|
307
|
+
return {
|
|
308
|
+
term: foundTerm.term,
|
|
309
|
+
content: foundTerm.definition,
|
|
310
|
+
sha: allTermsData.sha,
|
|
311
|
+
repository: {
|
|
312
|
+
owner: {
|
|
313
|
+
login: owner,
|
|
314
|
+
avatar_url: allTermsData.avatarUrl
|
|
315
|
+
},
|
|
316
|
+
name: repo
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
} else {
|
|
320
|
+
console.log(`❌ Term "${term}" not found in repository ${owner}/${repo}`);
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
module.exports = {
|
|
326
|
+
fetchTermsFromIndex,
|
|
327
|
+
fetchAllTermsFromIndex // Export the function to fetch all terms as well
|
|
328
|
+
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
|
-
const {
|
|
2
|
+
const { fetchTermsFromIndex, fetchAllTermsFromIndex } = require('./fetchTermsFromIndex.js');
|
|
3
3
|
const { matchTerm } = require('./matchTerm.js');
|
|
4
4
|
const { addPath, getPath, getAllPaths } = require('../config/paths');
|
|
5
|
+
const path = require('path');
|
|
5
6
|
|
|
6
7
|
// Directory to store cached files
|
|
7
8
|
const CACHE_DIR = getPath('githubcache');
|
|
8
9
|
|
|
9
10
|
async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, outputPathJS, outputPathJSTimeStamped, options) {
|
|
10
11
|
try {
|
|
11
|
-
|
|
12
12
|
// Clear the cache (remove the cache directory) if the cache option is set to false
|
|
13
13
|
if (options.cache === false) {
|
|
14
14
|
if (fs.existsSync(CACHE_DIR)) {
|
|
@@ -20,24 +20,78 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
|
|
|
20
20
|
if (!fs.existsSync(CACHE_DIR)) {
|
|
21
21
|
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
22
22
|
}
|
|
23
|
+
|
|
24
|
+
// Filter out incomplete xtrefs that don't have proper repository information
|
|
25
|
+
allXTrefs.xtrefs = allXTrefs.xtrefs.filter(xtref => {
|
|
26
|
+
if (!xtref.owner || !xtref.repo || !xtref.repoUrl) {
|
|
27
|
+
console.log(`⚠️ Removing incomplete reference: ${xtref.externalSpec}, ${xtref.term}`);
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Group xtrefs by repository to avoid multiple downloads of the same index.html
|
|
34
|
+
const xrefsByRepo = allXTrefs.xtrefs.reduce((groups, xtref) => {
|
|
35
|
+
const repoKey = `${xtref.owner}/${xtref.repo}`;
|
|
36
|
+
if (!groups[repoKey]) {
|
|
37
|
+
groups[repoKey] = {
|
|
38
|
+
owner: xtref.owner,
|
|
39
|
+
repo: xtref.repo,
|
|
40
|
+
xtrefs: []
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
groups[repoKey].xtrefs.push(xtref);
|
|
44
|
+
return groups;
|
|
45
|
+
}, {});
|
|
23
46
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
console.log(`✅ Grouped ${allXTrefs.xtrefs.length} terms into ${Object.keys(xrefsByRepo).length} repositories`);
|
|
48
|
+
|
|
49
|
+
// Process each repository once
|
|
50
|
+
for (const repoKey of Object.keys(xrefsByRepo)) {
|
|
51
|
+
const repoGroup = xrefsByRepo[repoKey];
|
|
52
|
+
console.log(`Processing repository: ${repoKey} (${repoGroup.xtrefs.length} terms)`);
|
|
53
|
+
|
|
54
|
+
// First, fetch all terms from this repository
|
|
55
|
+
const allTermsData = await fetchAllTermsFromIndex(
|
|
56
|
+
GITHUB_API_TOKEN,
|
|
57
|
+
repoGroup.owner,
|
|
58
|
+
repoGroup.repo,
|
|
59
|
+
options
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
if (!allTermsData) {
|
|
63
|
+
console.log(`❌ Could not fetch terms from repository ${repoKey}`);
|
|
64
|
+
// Mark all terms from this repo as not found
|
|
65
|
+
repoGroup.xtrefs.forEach(xtref => {
|
|
66
|
+
xtref.commitHash = "not found";
|
|
67
|
+
xtref.content = "This term was not found in the external repository.";
|
|
68
|
+
xtref.avatarUrl = null;
|
|
69
|
+
});
|
|
70
|
+
continue; // Skip to next repository
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Now process each term in this repository
|
|
74
|
+
for (const xtref of repoGroup.xtrefs) {
|
|
75
|
+
// Find the term in the pre-fetched data
|
|
76
|
+
const foundTerm = allTermsData.terms.find(
|
|
77
|
+
t => t.term.toLowerCase() === xtref.term.toLowerCase()
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
if (foundTerm) {
|
|
81
|
+
xtref.commitHash = allTermsData.sha;
|
|
82
|
+
xtref.content = foundTerm.definition;
|
|
83
|
+
xtref.avatarUrl = allTermsData.avatarUrl;
|
|
84
|
+
console.log(`✅ Match found for term: ${xtref.term} in ${xtref.externalSpec}`);
|
|
85
|
+
} else {
|
|
86
|
+
xtref.commitHash = "not found";
|
|
87
|
+
xtref.content = "This term was not found in the external repository.";
|
|
88
|
+
xtref.avatarUrl = null;
|
|
89
|
+
console.log(`ℹ️ No match found for term: ${xtref.term} in ${xtref.externalSpec}`);
|
|
90
|
+
}
|
|
40
91
|
}
|
|
92
|
+
|
|
93
|
+
console.log(`✅ Finished processing repository: ${repoKey}`);
|
|
94
|
+
console.log("============================================\n\n");
|
|
41
95
|
}
|
|
42
96
|
|
|
43
97
|
const allXTrefsStr = JSON.stringify(allXTrefs, null, 2);
|
|
@@ -46,6 +100,7 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
|
|
|
46
100
|
fs.writeFileSync(outputPathJS, stringReadyForFileWrite, 'utf8');
|
|
47
101
|
fs.writeFileSync(outputPathJSTimeStamped, stringReadyForFileWrite, 'utf8');
|
|
48
102
|
|
|
103
|
+
// This will run index.js
|
|
49
104
|
require('../../index.js')({ nowatch: true });
|
|
50
105
|
} catch (error) {
|
|
51
106
|
console.error("An error occurred:", error);
|