spec-up-t 1.0.91 → 1.1.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.
Files changed (51) hide show
  1. package/assets/compiled/body.js +1 -1
  2. package/assets/compiled/head.css +1 -1
  3. package/assets/css/index.css +202 -150
  4. package/index.js +0 -3
  5. package/package.json +2 -1
  6. package/src/add-remove-xref-source.js +171 -0
  7. package/src/{get-xtrefs-data.js → collect-external-references.js} +13 -42
  8. package/src/collectExternalReferences/fetchTermsFromGitHubRepository.js +237 -0
  9. package/src/{get-xtrefs-data → collectExternalReferences}/matchTerm.js +4 -3
  10. package/src/collectExternalReferences/processXTrefsData.js +53 -0
  11. package/src/config/paths.js +1 -0
  12. package/src/configure.js +96 -0
  13. package/src/init.js +2 -8
  14. package/src/install-from-boilerplate/add-scripts-keys.js +48 -0
  15. package/src/install-from-boilerplate/boilerplate/.env.example +2 -0
  16. package/src/install-from-boilerplate/boilerplate/.github/workflows/fetch-and-push-xrefs.yml +42 -0
  17. package/src/install-from-boilerplate/boilerplate/.github/workflows/render-specs.yml +47 -0
  18. package/src/install-from-boilerplate/boilerplate/README.md +3 -0
  19. package/src/install-from-boilerplate/boilerplate/assets/test.json +5 -0
  20. package/src/install-from-boilerplate/boilerplate/assets/test.text +1 -0
  21. package/src/install-from-boilerplate/boilerplate/gitignore +10 -0
  22. package/src/install-from-boilerplate/boilerplate/help.txt +6 -0
  23. package/src/install-from-boilerplate/boilerplate/main.sh +103 -0
  24. package/src/install-from-boilerplate/boilerplate/spec/example-markup-in-markdown.md +384 -0
  25. package/src/install-from-boilerplate/boilerplate/spec/spec-body.md +3 -0
  26. package/src/install-from-boilerplate/boilerplate/spec/spec-head.md +7 -0
  27. package/src/install-from-boilerplate/boilerplate/spec/terms-and-definitions-intro.md +5 -0
  28. package/src/install-from-boilerplate/boilerplate/spec/terms-definitions/term-1.md +13 -0
  29. package/src/install-from-boilerplate/boilerplate/spec/terms-definitions/term-2.md +3 -0
  30. package/src/install-from-boilerplate/boilerplate/spec/terms-definitions/term-3.md +3 -0
  31. package/src/install-from-boilerplate/boilerplate/spec/terms-definitions/term-4.md +3 -0
  32. package/src/install-from-boilerplate/boilerplate/specs.json +41 -0
  33. package/src/install-from-boilerplate/boilerplate/static/favicon.ico +0 -0
  34. package/src/install-from-boilerplate/boilerplate/static/logo.svg +236 -0
  35. package/src/install-from-boilerplate/config-scripts-keys.js +16 -0
  36. package/src/install-from-boilerplate/copy-boilerplate.js +22 -0
  37. package/src/install-from-boilerplate/install.js +8 -0
  38. package/src/install-from-boilerplate/postinstall-message.js +15 -0
  39. package/src/prepare-tref.js +21 -4
  40. package/src/utils/doesUrlExist.js +18 -10
  41. package/src/utils/isLineWithDefinition.js +13 -0
  42. package/src/get-xtrefs-data/matchTerm.1.js +0 -23
  43. package/src/get-xtrefs-data/searchGitHubCode.1.js +0 -69
  44. package/src/get-xtrefs-data/searchGitHubCode.2.js +0 -77
  45. package/src/get-xtrefs-data/searchGitHubCode.3.js +0 -85
  46. package/src/get-xtrefs-data/searchGitHubCode.4.js +0 -92
  47. package/src/get-xtrefs-data/searchGitHubCode.5.js +0 -97
  48. package/src/get-xtrefs-data/searchGitHubCode.6.js +0 -101
  49. package/src/get-xtrefs-data/searchGitHubCode.js +0 -97
  50. /package/src/{get-xtrefs-data → collectExternalReferences}/checkRateLimit.js +0 -0
  51. /package/src/{get-xtrefs-data → collectExternalReferences}/setupFetchHeaders.js +0 -0
@@ -0,0 +1,171 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ // Resolve the path to specs.json in the root directory
5
+ const JSON_FILE = path.resolve(process.cwd(), 'specs.json');
6
+
7
+ // Check if the JSON file exists
8
+ if (!fs.existsSync(JSON_FILE)) {
9
+ console.error(`Error: ${JSON_FILE} does not exist.`);
10
+ process.exit(1);
11
+ }
12
+
13
+ // Ask the user for inputs
14
+ const readline = require('readline');
15
+ const rl = readline.createInterface({
16
+ input: process.stdin,
17
+ output: process.stdout,
18
+ });
19
+
20
+ let mode = ""; // To track if user wants to add or remove
21
+
22
+ // Function to prompt for mode
23
+ function askMode() {
24
+ rl.question('Would you like to add, remove, or view entries? (add/remove/view): ', (answer) => {
25
+ mode = answer.trim().toLowerCase();
26
+ if (mode === 'add' || mode === 'a') {
27
+ askQuestion(0);
28
+ } else if (mode === 'remove' || mode === 'r') {
29
+ askRemoveEntry();
30
+ } else if (mode === 'view' || mode === 'v') {
31
+ showReferences();
32
+ } else {
33
+ console.log('Invalid option. Please enter add, remove, or view.');
34
+ askMode();
35
+ }
36
+ });
37
+ }
38
+
39
+ // Questions for adding an entry
40
+ const questions = [
41
+ 'Enter URL of the repository: ',
42
+ 'Enter URL of the GitHub page: ',
43
+ 'Enter the directory where the terms can be found: ',
44
+ 'Enter short unique name: ',
45
+ ];
46
+
47
+ let inputs = {};
48
+
49
+ // Function to ask questions for adding an entry
50
+ function askQuestion(index) {
51
+ if (index === questions.length) {
52
+ rl.close();
53
+ updateJSON();
54
+ return;
55
+ }
56
+
57
+ rl.question(questions[index], (answer) => {
58
+ switch (index) {
59
+ case 0:
60
+ inputs.url = answer;
61
+ break;
62
+ case 1:
63
+ inputs.gh_page = answer;
64
+ break;
65
+ case 2:
66
+ inputs.terms_dir = answer;
67
+ break;
68
+ case 3:
69
+ inputs.external_spec = answer;
70
+ break;
71
+ }
72
+ askQuestion(index + 1);
73
+ });
74
+ }
75
+
76
+ // Function to prompt for removal
77
+ function askRemoveEntry() {
78
+ rl.question('Enter the short unique name of the entry you want to remove: ', (answer) => {
79
+ removeEntry(answer.trim());
80
+ rl.close();
81
+ });
82
+ }
83
+
84
+ // Function to show current external references
85
+ function showReferences() {
86
+ const data = JSON.parse(fs.readFileSync(JSON_FILE, 'utf8'));
87
+ console.log('\nCurrent external references (xref):\n\n');
88
+
89
+ data.specs[0].external_specs.forEach(spec => {
90
+ console.log('--- External Reference: ---');
91
+ console.log(`Short name: ${spec.external_spec}`);
92
+ console.log(`GitHub Page: ${spec.gh_page}`);
93
+ console.log(`URL: ${spec.url}`);
94
+ console.log(`Terms Directory: ${spec.terms_dir}`);
95
+ console.log('\n');
96
+ });
97
+ rl.close();
98
+ }
99
+
100
+ // Update the JSON file by adding an entry
101
+ function updateJSON() {
102
+ try {
103
+ const data = JSON.parse(fs.readFileSync(JSON_FILE, 'utf8'));
104
+
105
+ if (!data.specs || !Array.isArray(data.specs) || !data.specs[0].external_specs) {
106
+ console.error('Error: Invalid JSON structure. "specs[0].external_specs" is missing.');
107
+ process.exit(1);
108
+ }
109
+
110
+ const externalSpecs = data.specs[0].external_specs;
111
+
112
+ // Check if the external_spec already exists
113
+ const exists = externalSpecs.some(
114
+ (entry) => entry.external_spec === inputs.external_spec
115
+ );
116
+
117
+ if (exists) {
118
+ console.log(
119
+ `Entry with external_spec "${inputs.external_spec}" already exists. No changes made.`
120
+ );
121
+ return;
122
+ }
123
+
124
+ // Add the new entry if it doesn't exist
125
+ externalSpecs.push(inputs);
126
+
127
+ fs.writeFileSync(JSON_FILE, JSON.stringify(data, null, 2), 'utf8');
128
+ // console.log(`Updated ${JSON_FILE} successfully.`);
129
+ console.log(`Updated successfully.`);
130
+ } catch (error) {
131
+ console.error(`Error: Failed to update ${JSON_FILE}.`, error.message);
132
+ process.exit(1);
133
+ }
134
+ }
135
+
136
+ // Remove an entry based on `external_spec`
137
+ function removeEntry(externalSpec) {
138
+ try {
139
+ const data = JSON.parse(fs.readFileSync(JSON_FILE, 'utf8'));
140
+
141
+ if (!data.specs || !Array.isArray(data.specs) || !data.specs[0].external_specs) {
142
+ console.error('Error: Invalid JSON structure. "specs[0].external_specs" is missing.');
143
+ process.exit(1);
144
+ }
145
+
146
+ const externalSpecs = data.specs[0].external_specs;
147
+
148
+ // Filter out the entry with the matching external_spec
149
+ const filteredSpecs = externalSpecs.filter(
150
+ (entry) => entry.external_spec !== externalSpec
151
+ );
152
+
153
+ if (filteredSpecs.length === externalSpecs.length) {
154
+ console.log(`No entry found with external_spec "${externalSpec}".`);
155
+ return;
156
+ }
157
+
158
+ // Update the JSON structure
159
+ data.specs[0].external_specs = filteredSpecs;
160
+
161
+ fs.writeFileSync(JSON_FILE, JSON.stringify(data, null, 2), 'utf8');
162
+ // console.log(`Removed entry with external_spec "${externalSpec}" successfully.`);
163
+ console.log(`Removed entry successfully.`);
164
+ } catch (error) {
165
+ console.error(`Error: Failed to update ${JSON_FILE}.`, error.message);
166
+ process.exit(1);
167
+ }
168
+ }
169
+
170
+ // Start by asking the mode
171
+ askMode();
@@ -11,11 +11,13 @@
11
11
  */
12
12
 
13
13
 
14
- function updateXTrefs(GITHUB_API_TOKEN, skipExisting) {
14
+ function collectExternalReferences(options = {}) {
15
+ require('dotenv').config();
15
16
  const fs = require('fs-extra');
16
17
  const readlineSync = require('readline-sync');
17
18
  const config = fs.readJsonSync('specs.json');
18
19
  const externalSpecsRepos = config.specs[0].external_specs;
20
+ const GITHUB_API_TOKEN = process.env.GITHUB_API_TOKEN;
19
21
 
20
22
  const explanationPAT =
21
23
  `\n SPEC-UP-T: No GitHub Personal Access Token (PAT) was found.
@@ -67,11 +69,8 @@ Please add external references to the specs.json file that you will find at the
67
69
  }
68
70
 
69
71
  function main() {
70
- const path = require('path');
71
- const { searchGitHubCode } = require('./get-xtrefs-data/searchGitHubCode.js');
72
- const { matchTerm } = require('./get-xtrefs-data/matchTerm');
72
+ const { processXTrefsData } = require('./collectExternalReferences/processXTrefsData.js');
73
73
  const { doesUrlExist } = require('./utils/doesUrlExist.js');
74
- const { addPath, getPath, getAllPaths } = require('./config/paths');
75
74
 
76
75
  // Check if the URLs for the external specs repositories are valid, and prompt the user to abort if they are not.
77
76
  externalSpecsRepos.forEach(repo => {
@@ -83,7 +82,7 @@ Please add external references to the specs.json file that you will find at the
83
82
 
84
83
  Terms directory: ${repo.terms_dir}
85
84
 
86
- Please fix the external references to the specs.json file that you will find at the root of your project.
85
+ Please fix the external references in the specs.json file that you will find at the root of your project.
87
86
 
88
87
  Do you want to stop? (yes/no): `);
89
88
  if (userInput.toLowerCase() === 'yes' || userInput.toLowerCase() === 'y') {
@@ -115,6 +114,7 @@ Please add external references to the specs.json file that you will find at the
115
114
  const outputPathJSON = 'output/xtrefs-data.json';
116
115
  const outputPathJS = 'output/xtrefs-data.js';
117
116
  const outputPathJSTimeStamped = 'output/xtrefs-history/xtrefs-data-' + Date.now() + '.js';
117
+
118
118
  // Function to extend xtref objects with additional information, such as repository URL and directory information.
119
119
  function extendXTrefs(config, xtrefs) {
120
120
  if (config.specs[0].external_specs_repos) {
@@ -137,6 +137,7 @@ Please add external references to the specs.json file that you will find at the
137
137
  const urlParts = new URL(xtref.repoUrl).pathname.split('/');
138
138
  xtref.owner = urlParts[1];
139
139
  xtref.repo = urlParts[2];
140
+ xtref.avatarUrl = repo.avatar_url;
140
141
  }
141
142
  });
142
143
 
@@ -178,7 +179,9 @@ Please add external references to the specs.json file that you will find at the
178
179
  // If the output JSON file exists, load its data.
179
180
  if (fs.existsSync(outputPathJSON)) {
180
181
  const existingXTrefs = fs.readJsonSync(outputPathJSON);
181
- allXTrefs = existingXTrefs && existingXTrefs.xtrefs ? existingXTrefs : { xtrefs: [] };
182
+ if (existingXTrefs && existingXTrefs.xtrefs) {
183
+ allXTrefs = existingXTrefs;
184
+ }
182
185
  }
183
186
 
184
187
  // Collect all markdown content
@@ -236,46 +239,14 @@ Please add external references to the specs.json file that you will find at the
236
239
  // }
237
240
  // ]
238
241
 
239
- (async () => {
240
- try {
241
- for (let xtref of allXTrefs.xtrefs) {
242
- const fetchedData = await searchGitHubCode(GITHUB_API_TOKEN, xtref.term, xtref.owner, xtref.repo, xtref.terms_dir);
243
- if (fetchedData.data.items.length === 0) {
244
- xtref.commitHash = "not found";
245
- xtref.content = "This term was not found in the external repository.";
246
- } else {
247
- fetchedData.data.items.forEach(item => {
248
- // If the term is found according to the matchTerm function (in the first line, line should start with “[[def:), etc) add the commit hash and content to the xtref object
249
- if (matchTerm(item.content, xtref.term)) {
250
- xtref.commitHash = item.sha;
251
- xtref.content = item.content;
252
- console.log(`\n SPEC-UP-T: Match found for term: ${xtref.term} in ${xtref.externalSpec};`);
253
- } else {
254
- xtref.commitHash = "not found";
255
- xtref.content = "This term was not found in the external repository.";
256
- console.log(`\n SPEC-UP-T: No match found for term: ${xtref.term} in ${xtref.externalSpec};`);
257
- }
258
- });
259
- }
260
- }
261
-
262
- const allXTrefsStr = JSON.stringify(allXTrefs, null, 2);
263
- fs.writeFileSync(outputPathJSON, allXTrefsStr, 'utf8');
264
- const stringReadyForFileWrite = `const allXTrefs = ${allXTrefsStr};`;
265
- fs.writeFileSync(outputPathJS, stringReadyForFileWrite, 'utf8');
266
- fs.writeFileSync(outputPathJSTimeStamped, stringReadyForFileWrite, 'utf8');
242
+
243
+ processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, outputPathJS, outputPathJSTimeStamped, options);
267
244
 
268
- // Run the render function to update the HTML file
269
- require('../index.js')({ nowatch: true });
270
- } catch (error) {
271
- console.error('An error occurred:', error);
272
- }
273
- })();
274
245
  }
275
246
 
276
247
 
277
248
  }
278
249
 
279
250
  module.exports = {
280
- updateXTrefs
251
+ collectExternalReferences
281
252
  }
@@ -0,0 +1,237 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const crypto = require('crypto'); // For generating cache keys
4
+ const isLineWithDefinition = require('../utils/isLineWithDefinition').isLineWithDefinition;
5
+ const { addPath, getPath, getAllPaths } = require('../config/paths');
6
+
7
+ // Directory to store cached files
8
+ const CACHE_DIR = getPath('githubcache');
9
+
10
+ // Helper function to generate a cache key
11
+ function generateCacheKey(...args) {
12
+ const hash = crypto.createHash('md5').update(args.join('-')).digest('hex');
13
+ return hash;
14
+ }
15
+
16
+ async function fetchTermsFromGitHubRepository(GITHUB_API_TOKEN, searchString, owner, repo, subdirectory) {
17
+ const { Octokit } = await import("octokit");
18
+ const { throttling } = await import("@octokit/plugin-throttling");
19
+
20
+ // Create a throttled Octokit instance
21
+ const ThrottledOctokit = Octokit.plugin(throttling);
22
+ const octokit = new ThrottledOctokit({
23
+ auth: GITHUB_API_TOKEN,
24
+ throttle: {
25
+ onRateLimit: (retryAfter, options) => {
26
+ console.warn(`Request quota exhausted for request ${options.method} ${options.url}`);
27
+ if (options.request.retryCount <= 1) {
28
+ console.log(`Retrying after ${retryAfter} seconds...`);
29
+ return true;
30
+ }
31
+ },
32
+ onAbuseLimit: (retryAfter, options) => {
33
+ console.warn(`Abuse detected for request ${options.method} ${options.url}`);
34
+ },
35
+ onSecondaryRateLimit: (retryAfter, options) => {
36
+ console.warn(`Secondary rate limit hit for request ${options.method} ${options.url}`);
37
+ if (options.request.retryCount <= 1) {
38
+ console.log(`Retrying after ${retryAfter} seconds...`);
39
+ return true;
40
+ }
41
+ },
42
+ },
43
+ });
44
+
45
+ try {
46
+ // Generate a cache key for the search query
47
+ const searchCacheKey = generateCacheKey('search', searchString, owner, repo, subdirectory);
48
+ const searchCacheFilePath = path.join(CACHE_DIR, `${searchCacheKey}.json`);
49
+
50
+ let searchResponse;
51
+
52
+ // Check if the search response is already cached
53
+ if (fs.existsSync(searchCacheFilePath)) {
54
+ console.log(`Serving search results from cache: ${searchCacheFilePath}`);
55
+ searchResponse = JSON.parse(fs.readFileSync(searchCacheFilePath, 'utf-8'));
56
+ } else {
57
+ // Perform the search using Octokit with exact match
58
+ console.log(`Performing search and caching results: ${searchCacheFilePath}`);
59
+ searchResponse = await octokit.rest.search.code({
60
+ q: `"${searchString}" repo:${owner}/${repo} path:${subdirectory}`, // Exact match in subdirectory
61
+ headers: {
62
+ Accept: "application/vnd.github.v3.text-match+json", // Include text-match media type
63
+ },
64
+ });
65
+
66
+ // Cache the search response
67
+ fs.writeFileSync(searchCacheFilePath, JSON.stringify(searchResponse), 'utf-8');
68
+ }
69
+
70
+ // Log the search results
71
+ console.log(`Total matches for ${searchString} :`, searchResponse.data.total_count);
72
+
73
+ /*
74
+
75
+ Each item is a file that contains the search string one or more times. So if a search string is found in 'attribute-based-access-control.md' and 'abac.md', both files will be returned as separate items. Each item contains “text_matches”.
76
+
77
+ - text_matches can contain multiple objects if there are multiple matches in the file.
78
+ - fragment is a snippet of the file content around the matched search string, not the entire file content.
79
+
80
+ In example below:
81
+
82
+ - The total_count is 2, indicating there are two files that contain the search string.
83
+ - Each item in the items array represents a file.
84
+ - The text_matches array within each item contains objects representing different matches of the search string within the file.
85
+ - Each fragment is a snippet of the file content around the matched search string, not the entire file content.
86
+
87
+ {
88
+ "total_count": 2,
89
+ "items": [
90
+ {
91
+ "name": "example-file1.md",
92
+ "path": "docs/example-file1.md",
93
+ "sha": "abc123",
94
+ "url": "https://api.github.com/repositories/123456789/contents/docs/example-file1.md",
95
+ "git_url": "https://api.github.com/repositories/123456789/git/blobs/abc123",
96
+ "html_url": "https://github.com/owner/repo/blob/main/docs/example-file1.md",
97
+ "repository": {
98
+ "id": 123456789,
99
+ "name": "repo",
100
+ "full_name": "owner/repo",
101
+ "owner": {
102
+ "login": "owner",
103
+ "id": 12345,
104
+ "avatar_url": "https://avatars.githubusercontent.com/u/12345?v=4",
105
+ "url": "https://api.github.com/users/owner"
106
+ }
107
+ },
108
+ "text_matches": [
109
+ {
110
+ "object_url": "https://api.github.com/repositories/123456789/contents/docs/example-file1.md",
111
+ "object_type": "FileContent",
112
+ "property": "content",
113
+ "fragment": "This is an example content with the search string.",
114
+ "matches": [
115
+ {
116
+ "text": "search string",
117
+ "indices": [31, 44]
118
+ }
119
+ ]
120
+ },
121
+ {
122
+ "object_url": "https://api.github.com/repositories/123456789/contents/docs/example-file1.md",
123
+ "object_type": "FileContent",
124
+ "property": "content",
125
+ "fragment": "Another occurrence of the search string in the file.",
126
+ "matches": [
127
+ {
128
+ "text": "search string",
129
+ "indices": [25, 38]
130
+ }
131
+ ]
132
+ }
133
+ ]
134
+ },
135
+ {
136
+ "name": "example-file2.md",
137
+ "path": "docs/example-file2.md",
138
+ "sha": "def456",
139
+ "url": "https://api.github.com/repositories/123456789/contents/docs/example-file2.md",
140
+ "git_url": "https://api.github.com/repositories/123456789/git/blobs/def456",
141
+ "html_url": "https://github.com/owner/repo/blob/main/docs/example-file2.md",
142
+ "repository": {
143
+ "id": 123456789,
144
+ "name": "repo",
145
+ "full_name": "owner/repo",
146
+ "owner": {
147
+ "login": "owner",
148
+ "id": 12345,
149
+ "avatar_url": "https://avatars.githubusercontent.com/u/12345?v=4",
150
+ "url": "https://api.github.com/users/owner"
151
+ }
152
+ },
153
+ "text_matches": [
154
+ {
155
+ "object_url": "https://api.github.com/repositories/123456789/contents/docs/example-file2.md",
156
+ "object_type": "FileContent",
157
+ "property": "content",
158
+ "fragment": "This file also contains the search string.",
159
+ "matches": [
160
+ {
161
+ "text": "search string",
162
+ "indices": [25, 38]
163
+ }
164
+ ]
165
+ }
166
+ ]
167
+ }
168
+ ]
169
+ }
170
+ */
171
+
172
+ for (const item of searchResponse.data.items) {
173
+ // Check if text_matches exists and is not empty
174
+ if (!item.text_matches || item.text_matches.length === 0) {
175
+ continue;
176
+ }
177
+
178
+ // Loop through each text match. Can contain multiple fragments
179
+ for (const match of item.text_matches) {
180
+ // Split the fragment into lines, lines can be empty ('')
181
+ const lines = match.fragment.split("\n");
182
+ for (const line of lines) {
183
+ if (isLineWithDefinition(line)) {
184
+ // Generate a unique cache key for the file
185
+ const fileCacheKey = generateCacheKey('file', owner, repo, item.path);
186
+ const fileCacheFilePath = path.join(CACHE_DIR, `${fileCacheKey}.txt`);
187
+
188
+ let fileContent;
189
+
190
+ // Check if the file is already cached
191
+ if (fs.existsSync(fileCacheFilePath)) {
192
+ console.log(`Serving file from cache: ${fileCacheFilePath}`);
193
+ fileContent = fs.readFileSync(fileCacheFilePath, 'utf-8');
194
+ } else {
195
+ // Fetch file content from GitHub
196
+ console.log(`Downloading and caching file: ${fileCacheFilePath}`);
197
+ try {
198
+ const fileContentResponse = await octokit.rest.repos.getContent({
199
+ owner: item.repository.owner.login, // Repository owner
200
+ repo: item.repository.name, // Repository name
201
+ path: item.path, // File path
202
+ });
203
+
204
+ // Decode the file content (it's base64-encoded)
205
+ if (fileContentResponse.data.content) {
206
+ fileContent = Buffer.from(fileContentResponse.data.content, "base64").toString("utf-8");
207
+ // Save the file to the cache
208
+ fs.writeFileSync(fileCacheFilePath, fileContent, 'utf-8');
209
+ } else {
210
+ // If the file is larger than 1 MB, GitHub's API will return a download URL instead of the content.
211
+ console.log("File is too large. Download URL:", fileContentResponse.data.download_url);
212
+ fileContent = "";
213
+ }
214
+ } catch (error) {
215
+ console.error(`Error fetching content for ${item.path}:`, error);
216
+ fileContent = ""; // Set content to an empty string if there's an error
217
+ }
218
+ }
219
+
220
+ // Attach the content to the item
221
+ item.content = fileContent;
222
+
223
+ // Return the item as soon as we find the correct line
224
+ return item;
225
+ }
226
+ }
227
+ }
228
+ }
229
+ } catch (error) {
230
+ console.error("Error searching GitHub or fetching file content:", error);
231
+ }
232
+
233
+ // If no item is found, return null or undefined
234
+ return null;
235
+ }
236
+
237
+ exports.fetchTermsFromGitHubRepository = fetchTermsFromGitHubRepository;
@@ -1,3 +1,5 @@
1
+ const isLineWithDefinition = require('../utils/isLineWithDefinition').isLineWithDefinition;
2
+
1
3
  function matchTerm(text, term) {
2
4
  if (!text || typeof text !== 'string') {
3
5
  // console.error('Invalid text:', text);
@@ -7,11 +9,10 @@ function matchTerm(text, term) {
7
9
 
8
10
  const firstLine = text.split('\n')[0].trim();
9
11
 
10
- // Check if the string starts with `[[def:` and ends with `]]`
11
- if (!firstLine.startsWith('[[def:') || !firstLine.endsWith(']]')) {
12
+ if (isLineWithDefinition(firstLine) === false) {
12
13
  console.log('String does not start with `[[def:` or end with `]]`');
13
14
  return false;
14
- }
15
+ };
15
16
 
16
17
  // Remove `[[def:` from the beginning and `]]` from the end
17
18
  let relevantPart = firstLine.slice(7, -2);
@@ -0,0 +1,53 @@
1
+ const fs = require('fs');
2
+ const { fetchTermsFromGitHubRepository } = require('./fetchTermsFromGitHubRepository.js');
3
+ const { matchTerm } = require('./matchTerm.js');
4
+ const { addPath, getPath, getAllPaths } = require('../config/paths');
5
+
6
+ // Directory to store cached files
7
+ const CACHE_DIR = getPath('githubcache');
8
+
9
+ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, outputPathJS, outputPathJSTimeStamped, options) {
10
+ try {
11
+
12
+ // Clear the cache (remove the cache directory) if the cache option is set to false
13
+ if (options.cache === false) {
14
+ if (fs.existsSync(CACHE_DIR)) {
15
+ fs.rmdirSync(CACHE_DIR, { recursive: true });
16
+ }
17
+ }
18
+
19
+ // Ensure the cache directory exists, so that we can store the fetched data
20
+ if (!fs.existsSync(CACHE_DIR)) {
21
+ fs.mkdirSync(CACHE_DIR, { recursive: true });
22
+ }
23
+
24
+ for (let xtref of allXTrefs.xtrefs) {
25
+ // Go and look if the term is in the external repository and if so, get the commit hash, and other meta info plus the content of the file
26
+ const item = await fetchTermsFromGitHubRepository(GITHUB_API_TOKEN, xtref.term, xtref.owner, xtref.repo, xtref.terms_dir, options);
27
+
28
+ // // Check if fetchedData.data is defined
29
+ if (item !== null && matchTerm(item.content, xtref.term)) {
30
+ xtref.commitHash = item.sha;
31
+ xtref.content = item.content;
32
+ xtref.avatarUrl = item.repository.owner.avatar_url;
33
+ console.log(`\n SPEC-UP-T: Match found for term: ${xtref.term} in ${xtref.externalSpec};`);
34
+ } else {
35
+ xtref.commitHash = "not found";
36
+ xtref.content = "This term was not found in the external repository.";
37
+ console.log(`\n SPEC-UP-T: No match found for term: ${xtref.term} in ${xtref.externalSpec};`);
38
+ }
39
+ }
40
+
41
+ const allXTrefsStr = JSON.stringify(allXTrefs, null, 2);
42
+ fs.writeFileSync(outputPathJSON, allXTrefsStr, 'utf8');
43
+ const stringReadyForFileWrite = `const allXTrefs = ${allXTrefsStr};`;
44
+ fs.writeFileSync(outputPathJS, stringReadyForFileWrite, 'utf8');
45
+ fs.writeFileSync(outputPathJSTimeStamped, stringReadyForFileWrite, 'utf8');
46
+
47
+ require('../../index.js')({ nowatch: true });
48
+ } catch (error) {
49
+ console.error("An error occurred:", error);
50
+ }
51
+ }
52
+
53
+ module.exports.processXTrefsData = processXTrefsData;
@@ -14,6 +14,7 @@ const paths = {};
14
14
  // Add paths
15
15
  paths['output-local-terms'] = path.resolve('output', 'local-terms-dir');
16
16
  paths['specsjson'] = path.resolve('specs.json');
17
+ paths['githubcache'] = path.resolve('output', 'github-cache');
17
18
 
18
19
  module.exports = {
19
20
  /**