spec-up-t 1.0.5 → 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.
- package/assets/compiled/body.js +45 -5
- package/assets/compiled/head.css +3 -2
- package/assets/css/bootstrap-parts.css +45 -9
- package/assets/css/modal.css +35 -0
- package/assets/css/xrefs.css +5 -1
- package/assets/js/collapse-definitions.js +1 -6
- package/assets/js/diff.min.js +37 -0
- package/assets/js/edit-term-buttons.js +1 -1
- package/assets/js/modal.js +52 -0
- package/assets/js/notyf.js +11 -2
- package/assets/js/show-commit-hashes.js +114 -22
- package/assets/js/token-input.js +30 -0
- package/package.json +2 -1
- package/spec/terms-definitions/term-1.md +2 -0
- package/specs.json +0 -8
- package/src/asset-map.json +4 -0
- package/src/create-pdf.js +1 -1
- package/src/get-xrefs-data.js +131 -212
package/src/get-xrefs-data.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
59
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
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:
|
|
138
|
+
console.error(`\n SPEC-UP-T: Error fetching file content: ${error.message}\n`);
|
|
125
139
|
}
|
|
140
|
+
return null;
|
|
126
141
|
}
|
|
127
142
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
227
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
//
|
|
270
|
-
|
|
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
|
-
//
|
|
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
|
+
}
|