spec-up-t 1.0.6 → 1.0.8

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.
@@ -5,285 +5,204 @@
5
5
  * @since 2024-06-09
6
6
  */
7
7
 
8
+
8
9
  const fs = require('fs-extra');
9
10
  const config = fs.readJsonSync('specs.json');
10
11
 
11
12
  // Collect all directories that contain files with a term and definition
13
+ // This maps over the specs in the config file and constructs paths to directories
14
+ // where the term definition files are located.
12
15
  const specTermsDirectories = config.specs.map(spec => spec.spec_directory + '/' + spec.spec_terms_directory);
13
16
 
14
- // Create directory named output in the project root if it does not yet exist
17
+ // Ensure that the 'output' directory exists, creating it if necessary.
15
18
  if (!fs.existsSync('output')) {
16
19
  fs.mkdirSync('output');
17
20
  }
18
21
 
19
- // Create directory named output/xrefs-history in the project root if it does not yet exist
22
+ // Ensure that the 'output/xrefs-history' directory exists, creating it if necessary.
20
23
  if (!fs.existsSync('output/xrefs-history')) {
21
24
  fs.mkdirSync('output/xrefs-history');
22
25
  }
23
26
 
24
- // Create a path for the output file in the project root
27
+ // Define paths for various output files, including JSON and JS files.
25
28
  const outputPathJSON = 'output/xrefs-data.json';
26
29
  const outputPathJS = 'output/xrefs-data.js';
27
30
  const outputPathJSTimeStamped = 'output/xrefs-history/xrefs-data-' + Date.now() + '.js';
28
31
 
29
- function getXrefsData() {
30
- let allXrefs = {};
31
- allXrefs.xrefs = new Set();
32
-
33
- // Function to fetch the latest commit hash of the file
34
- async function fetchLatestCommitHash(match) {
35
- /* Example
36
- console.log('match: ', match); ->
37
-
38
- match: {
39
- externalSpec: 'test-1',
40
- term: 'Aal',
41
- repoUrl: 'https://github.com/blockchainbird/spec-up-xref-test-1',
42
- terms_dir: 'spec/term-definitions',
43
- owner: 'blockchainbird',
44
- repo: 'spec-up-xref-test-1',
45
- site: 'https://blockchainbird.github.io/spec-up-xref-test-1/'
46
- }
47
- */
48
32
 
49
-
50
-
51
- try {
33
+ function getXrefsData(GITHUB_API_TOKEN) {
34
+ // Set headers for GitHub API requests. Include an authorization token if provided.
35
+ const fetchHeaders = {
36
+ 'Accept': 'application/vnd.github.v3+json'
37
+ };
52
38
 
53
- if (match.repoUrl === undefined) {
54
- console.log('\n SPEC-UP-T: match.repoUrl is undefined' + "\n");
55
- return;
56
- }
39
+ if (GITHUB_API_TOKEN) {
40
+ fetchHeaders['Authorization'] = `token ${GITHUB_API_TOKEN}`;
41
+ } else {
42
+ console.log('\n SPEC-UP-T: There is no GitHub token set up. Therefore, you are more likely to be at your limit of GitHub API requests. If you run into the limit, create a token and search the documentation on this topic.\n');
43
+ }
57
44
 
58
- // prerequisite: filename should be the term in the match object with spaces replaced by dashes and all lowercase
59
- const url = `https://api.github.com/repos/${match.owner}/${match.repo}/commits?path=${match.terms_dir}/${match.term.replace(/ /g, '-').toLowerCase()}.md`;
45
+ // Function to check the rate limit of the GitHub API
46
+ function checkRateLimit(response) {
47
+ if (response.status === 403 && response.headers.get('X-RateLimit-Remaining') === '0') {
48
+ const resetTime = new Date(response.headers.get('X-RateLimit-Reset') * 1000);
49
+ console.error(`\n SPEC-UP-T: Github API rate limit exceeded. Try again after ${resetTime}. See https://blockchainbird.github.io/spec-up-t-website/docs/github-token/ for more info.` + "\n");
50
+ return true;
51
+ } else {
52
+ console.log(`\n SPEC-UP-T: Github API rate limit: ${response.headers.get('X-RateLimit-Remaining')} requests remaining. See https://blockchainbird.github.io/spec-up-t-website/docs/github-token/ for more info.` + "\n");
53
+ }
54
+ return false;
55
+ }
60
56
 
61
- // Fetch the list of commits for the specified file
62
- const response = await fetch(url, {
63
- headers: {
64
- 'Accept': 'application/vnd.github.v3+json'
65
- }
66
- });
57
+ // Function to fetch term information from GitHub, including commit hash and content.
58
+ async function fetchTermInfoFromGithub(xref) {
59
+ try {
60
+ // prerequisite: filename should be the term in the match object with spaces replaced by dashes and all lowercase
61
+ //TODO: Loop through all markdown files to find the term and get the filename, instead of assuming that the filename is the term with spaces replaced by dashes and all lowercase
62
+ const url = `https://api.github.com/repos/${xref.owner}/${xref.repo}/commits?path=${xref.terms_dir}/${xref.term.replace(/ /g, '-').toLowerCase()}.md&per_page=1`;
63
+ const response = await fetch(url, { headers: fetchHeaders });
67
64
 
68
65
  // Check for rate limit before proceeding
69
- if (response.status === 403 && response.headers.get('X-RateLimit-Remaining') === '0') {
70
- const resetTime = new Date(response.headers.get('X-RateLimit-Reset') * 1000);
71
- console.error(`\n SPEC-UP-T: Github API rate limit exceeded. Try again after ${resetTime}` + "\n");
66
+ if (checkRateLimit(response)) {
72
67
  return;
73
68
  }
74
69
 
75
- // Check if the request was successful
76
- if (!response.ok) {
77
- throw new Error(`HTTP error! status: ${response.status}`);
78
- }
79
-
80
- console.log(`\n SPEC-UP-T: Github API request for:\n Term ${match.term},\n Name: ${match.externalSpec}\n Owner ${match.owner}\n Repo ${match.repo}\n was successful` + "\n");
81
-
82
- // Extract JSON data from the response, see https://blockchainbird.github.io/spec-up-t-website/docs/various-roles/developers-guide/#example-of-api-response for example response
83
- const data = await response.json();
84
-
85
- // Check if there are any commits
86
- if (data.length === 0) {
87
- console.log(`\n SPEC-UP-T: No commit hash found for the term “${match.term}”` + "\n");
88
-
89
- return;
70
+ if (response.ok) {
71
+ const data = await response.json();
72
+ if (data.length > 0) {
73
+ const commitHash = data[0].sha;
74
+ const content = await fetchFileContentFromCommit(xref.owner, xref.repo, commitHash, `${xref.terms_dir}/${xref.term.replace(/ /g, '-').toLowerCase()}.md`);
75
+ return { commitHash, content };
76
+ }
77
+ } else {
78
+ console.error(`\n SPEC-UP-T: Failed to fetch commit hash for ${xref.term}: ${response.statusText}\n`);
79
+ return { commitHash: null, content: null };
90
80
  }
81
+ } catch (error) {
82
+ console.error(`\n SPEC-UP-T: Error fetching data for term ${xref.term}: ${error.message}\n`);
83
+ }
84
+ return null;
85
+ }
91
86
 
92
- // Get only the last commit
93
- const commits = data.slice(0, 1);
94
- // Assign the fetched commit hash to the variable commitHash
95
- let commitHash = commits.map(commit => commit.sha);
96
-
97
- console.log(`\n SPEC-UP-T: Commit hash found for the term “${match.term}”: `, commitHash + "\n");
98
-
99
-
100
- //TODO: Check if a term is in the JSON file and not in the markdown file. If so, remove the term from the JSON file.
101
-
102
- // Check if the file exists
103
- if (fs.existsSync(outputPathJSON)) {
104
- // Read the JSON file
105
- let currentXrefs = fs.readJsonSync(outputPathJSON);
106
- // Check if the term is in the JSON file
107
- currentXrefs.xrefs.forEach(xref => {
108
- // Check if the term is in the JSON file
109
- if (xref.term === match.term) {
110
- // If the term is in the JSON file, get the commit hash from the file and assign it to the variable commitHash. This is done to prevent the commit hash from being overwritten by the fetched commit hash. We want to keep the commit hash that was fetched at the time that the author looked it up.
111
- console.log(`\n SPEC-UP-T: This external reference:\n Term: ${match.term}\n Name: ${match.externalSpec}\n Owner: ${match.owner}\n Repo: ${match.repo}\nis already referenced.
112
- ` + "\n")
113
-
114
- // Give the commitHash from the JSON file to the commitHash variable
115
- commitHash = xref.commitHash;
87
+ // Function to extend xref objects with additional information, such as repository URL and directory information.
88
+ function extendXrefs(config, xrefs) {
89
+ xrefs.forEach(xref => {
90
+ config.specs.forEach(spec => {
91
+ // Loop through "external_specs_repos" to find the repository URL for each xref
92
+ xref.repoUrl = null;
93
+ xref.terms_dir = null;
94
+ xref.owner = null;
95
+ xref.repo = null;
96
+
97
+ spec.external_specs_repos.forEach(repo => {
98
+ if (repo.external_spec === xref.externalSpec) {
99
+ xref.repoUrl = repo.url;
100
+ xref.terms_dir = repo.terms_dir;
101
+ const urlParts = new URL(xref.repoUrl).pathname.split('/');
102
+ xref.owner = urlParts[1];
103
+ xref.repo = urlParts[2];
116
104
  }
117
105
  });
118
- } else {
119
- console.log(`\n SPEC-UP-T: File not found at this point: ${outputPathJSON}. Don't worry, it will be created later.` + "\n");
120
- }
121
106
 
122
- return commitHash;
107
+ // Loop through "external_specs" to find the site URL for each xref
108
+
109
+ xref.site = null;
110
+ if (spec.external_specs) {
111
+ spec.external_specs.forEach(externalSpec => {
112
+ const key = Object.keys(externalSpec)[0];
113
+ if (key === xref.externalSpec) {
114
+ xref.site = externalSpec[key];
115
+ }
116
+ });
117
+ }
118
+ });
119
+ });
120
+ }
121
+
122
+ // Function to fetch the content of a file from a specific commit in a GitHub repository.
123
+ async function fetchFileContentFromCommit(owner, repo, commitHash, filePath) {
124
+ try {
125
+ const treeUrl = `https://api.github.com/repos/${owner}/${repo}/git/trees/${commitHash}?recursive=1`;
126
+ const treeResponse = await fetch(treeUrl, { headers: fetchHeaders });
127
+
128
+ if (treeResponse.ok) {
129
+ const treeData = await treeResponse.json();
130
+ const file = treeData.tree.find(item => item.path === filePath);
131
+ if (file) {
132
+ const fileContentResponse = await fetch(file.url);
133
+ const fileContentData = await fileContentResponse.json();
134
+ return Buffer.from(fileContentData.content, 'base64').toString('utf-8');
135
+ }
136
+ }
123
137
  } catch (error) {
124
- console.error(`\n SPEC-UP-T: Failed to fetch commit hash for the term “${match.term}”:`, error + "\n");
138
+ console.error(`\n SPEC-UP-T: Error fetching file content: ${error.message}\n`);
125
139
  }
140
+ return null;
126
141
  }
127
142
 
128
- async function fetchLatestCommitHashes() {
129
- for (const xref of allXrefs.xrefs) {
130
- xref.commitHash = await fetchLatestCommitHash(xref);
131
- }
143
+ // Initialize an object to store all xrefs. If the output JSON file exists, load its data.
144
+ let allXrefs = { xrefs: [] };
145
+
146
+ if (fs.existsSync(outputPathJSON)) {
147
+ const existingXrefs = fs.readJsonSync(outputPathJSON);
148
+ allXrefs = existingXrefs && existingXrefs.xrefs ? existingXrefs : { xrefs: [] };
132
149
  }
133
150
 
134
- // Go through all directories that contain files with a term and definition
151
+ // Loop through each directory and file, extracting xrefs from markdown files.
135
152
  specTermsDirectories.forEach(specDirectory => {
136
- console.log(`\n SPEC-UP-T: Current spec_directory: `, specDirectory + "\n");
137
- // read directory
138
153
  fs.readdirSync(specDirectory).forEach(file => {
139
- // read file
140
154
  if (file.endsWith('.md')) {
141
- console.log(`\n SPEC-UP-T: Markdown file referenced in spec_directory: `, file + "\n");
142
155
  const markdown = fs.readFileSync(`${specDirectory}/${file}`, 'utf8');
143
- // create regex that finds “[[xref:.*]]”
144
156
  const regex = /\[\[xref:.*?\]\]/g;
145
157
  if (regex.test(markdown)) {
146
158
  const xrefs = markdown.match(regex);
147
159
  xrefs.forEach(xref => {
148
- console.log(`\n SPEC-UP-T: Xref found in ${file}: `, xref + "\n");
149
- // example of xref: [xref: test-1, Aal]
150
- allXrefs.xrefs.add(xref);
160
+ const newXrefObj = processXref(xref);
161
+ if (!allXrefs.xrefs.some(existingXref =>
162
+ existingXref.term === newXrefObj.term && existingXref.externalSpec === newXrefObj.externalSpec)) {
163
+ allXrefs.xrefs.push(newXrefObj);
164
+ }
151
165
  });
152
166
  }
153
167
  }
154
168
  });
155
- })
156
- // Convert the Set back to an Array if needed
157
- allXrefs.xrefs = Array.from(allXrefs.xrefs);
158
-
159
- // Example output:
160
- // allXrefs.xrefs: [
161
- // '[[xref: test-1, Aal]]',
162
- // '[[xref: test-2, Abac]]'
163
- // ]
164
-
165
- // The following steps create an array of objects with the keys “externalSpec” and “term” for each xref by splitting the xref string on the comma and removing the “[[xref:” and “]]” parts
166
-
167
- // Step 1: remove “[[xref:” from the beginning of every value in allMatches
168
- allXrefs.xrefs = allXrefs.xrefs.map(xref => {
169
- return xref.replace(/\[\[xref:/, '');
170
- });
171
-
172
- // Step 2: remove “]]” from the end of every value in allMatches
173
- allXrefs.xrefs = allXrefs.xrefs.map(xref => {
174
- return xref.replace(/\]\]/, '');
175
169
  });
176
170
 
177
- // Step 3: trim every entry of allMatches
178
- allXrefs.xrefs = allXrefs.xrefs.map(xref => {
179
- return xref.trim();
180
- });
181
-
182
- // Step 4: split every entry of allMatches on the first comma, replace the entry with an object that has two keys: one that contains everything before the comma and one that contains everything after the comma
183
- allXrefs.xrefs = allXrefs.xrefs.map(xref => {
184
- let [externalSpec, term] = xref.split(/,/, 2);
171
+ // Function to process and clean up xref strings, returning an object with `externalSpec` and `term` properties.
172
+ function processXref(xref) {
173
+ let [externalSpec, term] = xref.replace(/\[\[xref:/, '').replace(/\]\]/, '').trim().split(/,/, 2);
185
174
  return {
186
175
  externalSpec: externalSpec.trim(),
187
176
  term: term.trim()
188
177
  };
189
- })
190
-
191
- // Example output:
192
- // allXrefs.xrefs: [
193
- // { externalSpec: 'test-1', term: 'Aal' },
194
- // { externalSpec: 'test-2', term: 'Abac' }
195
- // ]
196
-
197
- // Step 5: add the url and the dir where the terms are, to the xref object
198
- allXrefs.xrefs.forEach(xref => {
199
- config.specs.forEach(spec => {
200
- spec.external_specs_repos.forEach(repo => {
201
- // if the externalSpec is in the config, add the url and the dir where the terms are, to the xref object
202
- // Example external_specs_repos:
203
- // "external_specs_repos": [
204
- // {
205
- // "external_spec": "test-1",
206
- // "url": "https://github.com/blockchainbird/spec-up-xref-test-1",
207
- // "terms_dir": "spec"
208
- // },
209
- // …
210
- // ]
211
- if (repo.external_spec === xref.externalSpec) {
212
- xref.repoUrl = repo.url;
213
- xref.terms_dir = repo.terms_dir;
214
- }
215
- });
216
- });
217
- });
218
-
219
- // Step 6: add the owner and repo to the xref object
220
- allXrefs.xrefs.forEach(xref => {
221
- if (xref.repoUrl === undefined) {
222
- console.log('\n SPEC-UP-T: match.repoUrl is undefined' + "\n");
223
- return;
224
- }
178
+ }
225
179
 
226
- const urlParts = new URL(xref.repoUrl).pathname.split('/');
227
- xref.owner = urlParts[1];
228
- xref.repo = urlParts[2];
229
- });
180
+ // Extend each xref with additional data and fetch commit information from GitHub.
181
+ extendXrefs(config, allXrefs.xrefs);
230
182
 
231
- // Step 7: add the site to the xref object
232
- allXrefs.xrefs.forEach(xref => {
233
- // loop through array of specs in config
234
- config.specs.forEach(spec => {
235
- if (spec.external_specs) {
236
- // Example external_specs:
237
- // "external_specs": [
238
- // {
239
- // "test-1": "https://blockchainbird.github.io/spec-up-xref-test-1/"
240
- // }
241
- // …
242
- // ]
243
- spec.external_specs.forEach(externalSpec => {
244
- const key = Object.keys(externalSpec)[0];
245
- if (key === xref.externalSpec) {
246
- xref.site = externalSpec[key];
247
- }
248
- });
183
+ async function fetchAllTermsInfoFromGithub() {
184
+ for (let xref of allXrefs.xrefs) {
185
+ if (!xref.commitHash || !xref.content) {
186
+ const fetchedData = await fetchTermInfoFromGithub(xref);
187
+ if (fetchedData) {
188
+ xref.commitHash = fetchedData.commitHash;
189
+ xref.content = fetchedData.content;
190
+ }
249
191
  }
250
- });
251
- });
252
-
253
- // Loop through all xrefs and fetch the latest commit hash for each term and add it to the xref object
254
- /* Example of xref after adding commitHash:
255
- xref: {
256
- "externalSpec": "test-1",
257
- "term": "Aal",
258
- "repoUrl": "https://github.com/blockchainbird/spec-up-xref-test-1",
259
- "terms_dir": "spec/term-definitions",
260
- "owner": "blockchainbird",
261
- "repo": "spec-up-xref-test-1",
262
- "site": "https://blockchainbird.github.io/spec-up-xref-test-1/",
263
- "commitHash": [
264
- "f66951f1d378490289caab9c51141b44a0438365"
265
- ]
266
192
  }
267
- */
193
+ }
268
194
 
269
- // Call the function and wait for it to complete before writing to the file
270
- fetchLatestCommitHashes().then(() => {
271
- // Convert allXrefsStr to a JSON string with indentation
195
+ // Fetch all term information, then write the results to JSON and JS files.
196
+ fetchAllTermsInfoFromGithub().then(() => {
272
197
  const allXrefsStr = JSON.stringify(allXrefs, null, 2);
273
-
274
- // // Write the JSON code to a .json file
275
198
  fs.writeFileSync(outputPathJSON, allXrefsStr, 'utf8');
276
-
277
- // Create the JS code for the assignment
278
199
  const stringReadyForFileWrite = `const allXrefs = ${allXrefsStr};`;
279
-
280
- // Write the JS code to a .js file
281
200
  fs.writeFileSync(outputPathJS, stringReadyForFileWrite, 'utf8');
282
201
  fs.writeFileSync(outputPathJSTimeStamped, stringReadyForFileWrite, 'utf8');
283
202
  });
284
203
  }
285
204
 
286
- // Write function that removes an entry from xrefs-data.json and xrefs-data.js based on the term and externalSpec
205
+ // Function to remove a specific xref from the JSON file, based on term and externalSpec.
287
206
  function removeXref(term, externalSpec) {
288
207
  let messages = [];
289
208
 
@@ -325,8 +244,8 @@ function removeXref(term, externalSpec) {
325
244
  return messages;
326
245
  }
327
246
 
328
-
247
+ // Export the getXrefsData and removeXref functions for use in other modules.
329
248
  module.exports = {
330
249
  getXrefsData,
331
250
  removeXref
332
- }
251
+ }