spec-up-t 1.2.9 → 1.3.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 (32) hide show
  1. package/.github/copilot-instructions.md +2 -1
  2. package/assets/compiled/body.js +5 -5
  3. package/assets/compiled/head.css +1 -0
  4. package/assets/css/counter.css +104 -0
  5. package/assets/js/addAnchorsToTerms.js +13 -5
  6. package/assets/js/collapse-definitions.js +0 -3
  7. package/assets/js/custom-elements.js +25 -27
  8. package/assets/js/fix-last-dd.js +6 -3
  9. package/assets/js/highlight-heading-plus-sibling-nodes.js +0 -1
  10. package/assets/js/highlight-heading-plus-sibling-nodes.test.js +120 -0
  11. package/assets/js/insert-trefs.js +32 -28
  12. package/config/asset-map.json +1 -0
  13. package/index.js +33 -227
  14. package/package.json +4 -2
  15. package/sonar-project.properties +7 -0
  16. package/src/collect-external-references.js +22 -11
  17. package/src/collect-external-references.test.js +153 -2
  18. package/src/collectExternalReferences/fetchTermsFromIndex.js +65 -110
  19. package/src/collectExternalReferences/processXTrefsData.js +9 -11
  20. package/src/create-docx.js +332 -0
  21. package/src/create-pdf.js +243 -122
  22. package/src/fix-markdown-files.js +31 -34
  23. package/src/html-dom-processor.js +290 -0
  24. package/src/init.js +3 -0
  25. package/src/install-from-boilerplate/boilerplate/.github/workflows/menu.yml +51 -36
  26. package/src/install-from-boilerplate/boilerplate/spec/example-markup-in-markdown.md +0 -1
  27. package/src/install-from-boilerplate/boilerplate/spec/terms-and-definitions-intro.md +1 -5
  28. package/src/install-from-boilerplate/config-scripts-keys.js +4 -4
  29. package/src/install-from-boilerplate/menu.sh +6 -6
  30. package/src/markdown-it-extensions.js +54 -33
  31. package/src/references.js +18 -6
  32. package/templates/template.html +2 -0
@@ -93,6 +93,46 @@ And here we reference it again using [[tref:kmg-1,authentic-chained-data-contain
93
93
  ### Conclusion
94
94
  That's all about these references.`,
95
95
  shouldMatch: true
96
+ },
97
+
98
+ // Test cases for aliases - the function should match when the original term exists regardless of alias
99
+ {
100
+ name: 'tref with alias should match based on original term',
101
+ xtref: { externalSpec: 'vlei1', term: 'vlei-ecosystem-governance-framework' },
102
+ markdown: '[[tref:vlei1, vlei-ecosystem-governance-framework, vEGF]]',
103
+ shouldMatch: true
104
+ },
105
+ {
106
+ name: 'xref with alias should match based on original term',
107
+ xtref: { externalSpec: 'vlei1', term: 'vlei-ecosystem-governance-framework' },
108
+ markdown: '[[xref:vlei1, vlei-ecosystem-governance-framework, vEGF]]',
109
+ shouldMatch: true
110
+ },
111
+ {
112
+ name: 'multiple aliases for same term should match',
113
+ xtref: { externalSpec: 'spec1', term: 'long-term-name' },
114
+ markdown: 'Text [[tref:spec1, long-term-name, alias1]] and [[tref:spec1, long-term-name, alias2]]',
115
+ shouldMatch: true
116
+ },
117
+ {
118
+ name: 'tref with spaces in alias should match',
119
+ xtref: { externalSpec: 'spec1', term: 'term1' },
120
+ markdown: '[[tref:spec1, term1, alias with spaces]]',
121
+ shouldMatch: true
122
+ },
123
+
124
+ // Test case for the specific issue with hyphens and spaces
125
+ {
126
+ name: 'external spec and term with hyphens and alias should match',
127
+ xtref: { externalSpec: 'vlei-glossary', term: 'vlei-ecosystem-governance-framework' },
128
+ markdown: '[[tref: vlei-glossary, vlei-ecosystem-governance-framework, vegf]]',
129
+ shouldMatch: true
130
+ },
131
+ {
132
+ name: 'external spec and term with hyphens without alias should match',
133
+ xtref: { externalSpec: 'vlei-glossary', term: 'vlei-ecosystem-governance-framework' },
134
+ markdown: '[[tref: vlei-glossary, vlei-ecosystem-governance-framework]]',
135
+ shouldMatch: true
96
136
  }
97
137
  ];
98
138
 
@@ -119,12 +159,15 @@ describe('addNewXTrefsFromMarkdown', () => {
119
159
  });
120
160
  });
121
161
 
122
- it('should not add duplicate xtrefs', () => {
123
- const markdownContent = "Content [[xref:specA, termA]] and again [[xref:specA, termA]]";
162
+ it('should not add duplicate xtrefs with same spec and term but different aliases', () => {
163
+ const markdownContent = "Content [[xref:specA, termA]] and again [[xref:specA, termA, aliasA]]";
124
164
  const allXTrefs = { xtrefs: [] };
125
165
  const updatedXTrefs = addNewXTrefsFromMarkdown(markdownContent, allXTrefs);
126
166
 
127
167
  expect(updatedXTrefs.xtrefs.length).toBe(1);
168
+ expect(updatedXTrefs.xtrefs[0].term).toBe('termA');
169
+ expect(updatedXTrefs.xtrefs[0].externalSpec).toBe('specA');
170
+ // The first one found will be used (without alias in this case)
128
171
  });
129
172
 
130
173
  it('should add multiple distinct xtrefs', () => {
@@ -149,4 +192,112 @@ describe('addNewXTrefsFromMarkdown', () => {
149
192
  expect(updatedXTrefs.xtrefs.length).toBe(0);
150
193
  });
151
194
 
195
+ it('should add a new tref with alias from markdown content', () => {
196
+ const markdownContent = "Some text [[tref:specA, termA, aliasA]] more text";
197
+ const allXTrefs = { xtrefs: [] };
198
+ const updatedXTrefs = addNewXTrefsFromMarkdown(markdownContent, allXTrefs);
199
+
200
+ expect(updatedXTrefs.xtrefs.length).toBe(1);
201
+ expect(updatedXTrefs.xtrefs[0]).toEqual({
202
+ externalSpec: 'specA',
203
+ term: 'termA',
204
+ alias: 'aliasA'
205
+ });
206
+ });
207
+
208
+ it('should add a new xref with alias from markdown content', () => {
209
+ const markdownContent = "Some text [[xref:specA, termA, aliasA]] more text";
210
+ const allXTrefs = { xtrefs: [] };
211
+ const updatedXTrefs = addNewXTrefsFromMarkdown(markdownContent, allXTrefs);
212
+
213
+ expect(updatedXTrefs.xtrefs.length).toBe(1);
214
+ expect(updatedXTrefs.xtrefs[0]).toEqual({
215
+ externalSpec: 'specA',
216
+ term: 'termA',
217
+ alias: 'aliasA'
218
+ });
219
+ });
220
+
221
+ it('should handle tref without alias (backwards compatibility)', () => {
222
+ const markdownContent = "Some text [[tref:specA, termA]] more text";
223
+ const allXTrefs = { xtrefs: [] };
224
+ const updatedXTrefs = addNewXTrefsFromMarkdown(markdownContent, allXTrefs);
225
+
226
+ expect(updatedXTrefs.xtrefs.length).toBe(1);
227
+ expect(updatedXTrefs.xtrefs[0]).toEqual({
228
+ externalSpec: 'specA',
229
+ term: 'termA'
230
+ });
231
+ expect(updatedXTrefs.xtrefs[0].alias).toBeUndefined();
232
+ });
233
+
234
+ });
235
+
236
+
237
+ describe('processXTref', () => {
238
+ const processXTref = require('./collect-external-references').processXTref;
239
+
240
+ it('should process basic xref without alias', () => {
241
+ const xtref = '[[xref:specA,termA]]';
242
+ const result = processXTref(xtref);
243
+
244
+ expect(result).toEqual({
245
+ externalSpec: 'specA',
246
+ term: 'termA'
247
+ });
248
+ });
249
+
250
+ it('should process basic tref without alias', () => {
251
+ const xtref = '[[tref:specA,termA]]';
252
+ const result = processXTref(xtref);
253
+
254
+ expect(result).toEqual({
255
+ externalSpec: 'specA',
256
+ term: 'termA'
257
+ });
258
+ });
259
+
260
+ it('should process tref with alias', () => {
261
+ const xtref = '[[tref:specA,termA,aliasA]]';
262
+ const result = processXTref(xtref);
263
+
264
+ expect(result).toEqual({
265
+ externalSpec: 'specA',
266
+ term: 'termA',
267
+ alias: 'aliasA'
268
+ });
269
+ });
270
+
271
+ it('should process xref with alias', () => {
272
+ const xtref = '[[xref:specA,termA,aliasA]]';
273
+ const result = processXTref(xtref);
274
+
275
+ expect(result).toEqual({
276
+ externalSpec: 'specA',
277
+ term: 'termA',
278
+ alias: 'aliasA'
279
+ });
280
+ });
281
+
282
+ it('should handle spaces in parameters', () => {
283
+ const xtref = '[[tref: specA , termA , aliasA ]]';
284
+ const result = processXTref(xtref);
285
+
286
+ expect(result).toEqual({
287
+ externalSpec: 'specA',
288
+ term: 'termA',
289
+ alias: 'aliasA'
290
+ });
291
+ });
292
+
293
+ it('should ignore empty alias parameter', () => {
294
+ const xtref = '[[tref:specA,termA,]]';
295
+ const result = processXTref(xtref);
296
+
297
+ expect(result).toEqual({
298
+ externalSpec: 'specA',
299
+ term: 'termA'
300
+ });
301
+ expect(result.alias).toBeUndefined();
302
+ });
152
303
  });
@@ -11,67 +11,11 @@ const path = require('path');
11
11
  const { JSDOM } = require('jsdom');
12
12
  const axios = require('axios');
13
13
  const { addPath, getPath, getAllPaths } = require('../../config/paths');
14
- const crypto = require('crypto');
15
14
 
16
- // Directory to store cached files
15
+ // Directory to store fetched data files
17
16
  const CACHE_DIR = getPath('githubcache');
18
17
 
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
18
 
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
- * @example
37
- * const cacheTTL = options.cacheTTL || 24 * 60 * 60 * 1000; // Default: 24 hours
38
- */
39
- function getFromCache(cacheKey, options = {}) {
40
- const cachePath = path.join(CACHE_DIR, `${cacheKey}.json`);
41
- const cacheTTL = 0;
42
-
43
- if (!fs.existsSync(cachePath)) {
44
- return null;
45
- }
46
-
47
- const cacheData = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
48
- const cacheTime = new Date(cacheData.timestamp).getTime();
49
- const currentTime = new Date().getTime();
50
-
51
- // Check if cache is expired
52
- if (currentTime - cacheTime > cacheTTL) {
53
- console.log(`Cache expired for key: ${cacheKey}`);
54
- return null;
55
- }
56
-
57
- console.log(`Using cached data for key: ${cacheKey}`);
58
- return cacheData;
59
- }
60
-
61
- /**
62
- * Saves data to cache
63
- * @param {string} cacheKey - Cache key
64
- * @param {object} data - Data to cache
65
- */
66
- function saveToCache(cacheKey, data) {
67
- const cachePath = path.join(CACHE_DIR, `${cacheKey}.json`);
68
- const cacheData = {
69
- timestamp: new Date().toISOString(),
70
- ...data
71
- };
72
- fs.writeFileSync(cachePath, JSON.stringify(cacheData, null, 2));
73
- console.log(`Saved to cache: ${cacheKey}`);
74
- }
75
19
 
76
20
  /**
77
21
  * Fetches the latest commit hash for a specific file in a repository
@@ -107,77 +51,93 @@ async function getFileCommitHash(token, owner, repo, filePath, headers) {
107
51
  }
108
52
 
109
53
  /**
110
- * Fetches all terms and definitions from a repository's index.html
54
+ * Fetches all terms and definitions from a repository's GitHub Pages index.html
111
55
  * @param {string} token - GitHub API Token
112
56
  * @param {string} owner - Repository owner
113
57
  * @param {string} repo - Repository name
114
- * @param {object} options - Additional options
58
+ * @param {object} options - Additional options including ghPageUrl
115
59
  * @returns {object|null} - Object containing all terms or null if error
116
60
  */
117
61
  async function fetchAllTermsFromIndex(token, owner, repo, options = {}) {
118
62
  try {
119
- // Generate cache key based on repo information
120
- const cacheKey = generateCacheKey(owner, repo);
121
- let cachedData = null;
122
-
123
- // Check cache first if caching is enabled
124
- if (options.cache !== false) {
125
- cachedData = getFromCache(cacheKey, options);
126
- if (cachedData) {
127
- return cachedData;
128
- }
129
- }
130
-
131
63
  // Configure headers for GitHub API
132
64
  const headers = {};
133
65
  if (token) {
134
66
  headers['Authorization'] = `token ${token}`;
135
67
  }
136
68
 
137
- // Get the specs.json content from the repository to find the output_path
138
- const specsJsonUrl = `https://api.github.com/repos/${owner}/${repo}/contents/specs.json`;
139
- console.log(`Fetching specs.json from: ${specsJsonUrl}`);
69
+ // Use GitHub Pages URL if provided in options, otherwise fallback to raw repository
70
+ let indexHtmlUrl;
71
+ let commitHash = null;
140
72
 
141
- // Fetch specs.json content
142
- const specsJsonResponse = await axios.get(specsJsonUrl, { headers });
143
- if (specsJsonResponse.status !== 200) {
144
- console.log(`❌ Could not find specs.json in repository ${owner}/${repo}`);
145
- return null;
146
- }
73
+ if (options.ghPageUrl) {
74
+ // Fetch from GitHub Pages (deployed HTML)
75
+ indexHtmlUrl = options.ghPageUrl.endsWith('/') ?
76
+ `${options.ghPageUrl}index.html` :
77
+ `${options.ghPageUrl}/index.html`;
78
+ console.log(`Fetching index.html from GitHub Pages: ${indexHtmlUrl}`);
79
+
80
+ // For GitHub Pages, we'll try to get the commit hash from the main branch
81
+ try {
82
+ const mainBranchUrl = `https://api.github.com/repos/${owner}/${repo}/branches/main`;
83
+ const branchResponse = await axios.get(mainBranchUrl, { headers });
84
+ if (branchResponse.status === 200) {
85
+ commitHash = branchResponse.data.commit.sha;
86
+ console.log(`✅ Got commit hash from main branch: ${commitHash}`);
87
+ }
88
+ } catch (error) {
89
+ console.log(`⚠️ Could not get commit hash from main branch: ${error.message}`);
90
+ }
91
+ } else {
92
+ // Fallback to raw repository method
93
+ console.log(`⚠️ No GitHub Pages URL provided, falling back to repository method`);
94
+
95
+ // Get the specs.json content from the repository to find the output_path
96
+ const specsJsonUrl = `https://api.github.com/repos/${owner}/${repo}/contents/specs.json`;
97
+ console.log(`Fetching specs.json from: ${specsJsonUrl}`);
147
98
 
148
- // Decode specs.json content from base64
149
- const specsJsonContent = Buffer.from(specsJsonResponse.data.content, 'base64').toString('utf8');
150
- const specsJson = JSON.parse(specsJsonContent);
151
-
152
- // Get the output_path from specs.json
153
- const outputPath = specsJson.specs[0].output_path;
154
- if (!outputPath) {
155
- console.log(`❌ No output_path found in specs.json for repository ${owner}/${repo}`);
156
- return null;
99
+ // Fetch specs.json content
100
+ const specsJsonResponse = await axios.get(specsJsonUrl, { headers });
101
+ if (specsJsonResponse.status !== 200) {
102
+ console.log(`❌ Could not find specs.json in repository ${owner}/${repo}`);
103
+ return null;
104
+ }
105
+
106
+ // Decode specs.json content from base64
107
+ const specsJsonContent = Buffer.from(specsJsonResponse.data.content, 'base64').toString('utf8');
108
+ const specsJson = JSON.parse(specsJsonContent);
109
+
110
+ // Get the output_path from specs.json
111
+ const outputPath = specsJson.specs[0].output_path;
112
+ if (!outputPath) {
113
+ console.log(`❌ No output_path found in specs.json for repository ${owner}/${repo}`);
114
+ return null;
115
+ }
116
+
117
+ // Fix: Properly normalize the output path to ensure it doesn't have leading "./" or trailing "/"
118
+ const normalizedOutputPath = outputPath.replace(/^\.\//, '').replace(/\/$/, '');
119
+
120
+ // Create the path to the index.html file
121
+ const indexHtmlPath = `${normalizedOutputPath}/index.html`;
122
+
123
+ // Fetch the index.html content with properly constructed URL
124
+ indexHtmlUrl = `https://raw.githubusercontent.com/${owner}/${repo}/main/${indexHtmlPath}`;
125
+ console.log(`Fetching index.html from raw repository: ${indexHtmlUrl}`);
126
+
127
+ // Get the commit hash for the index.html file
128
+ commitHash = await getFileCommitHash(token, owner, repo, indexHtmlPath, headers);
129
+ if (!commitHash) {
130
+ console.log(`⚠️ Could not get commit hash for index.html, continuing without it`);
131
+ }
157
132
  }
158
133
 
159
- // Fix: Properly normalize the output path to ensure it doesn't have leading "./" or trailing "/"
160
- const normalizedOutputPath = outputPath.replace(/^\.\//, '').replace(/\/$/, '');
161
-
162
- // Create the path to the index.html file
163
- const indexHtmlPath = `${normalizedOutputPath}/index.html`;
164
-
165
- // Fetch the index.html content with properly constructed URL
166
- const indexHtmlUrl = `https://raw.githubusercontent.com/${owner}/${repo}/main/${indexHtmlPath}`;
167
- console.log(`Fetching index.html from: ${indexHtmlUrl}`);
168
-
134
+ // Fetch the index.html content
169
135
  const indexHtmlResponse = await axios.get(indexHtmlUrl, { headers });
170
136
  if (indexHtmlResponse.status !== 200) {
171
137
  console.log(`❌ Could not find index.html at ${indexHtmlUrl}`);
172
138
  return null;
173
139
  }
174
140
 
175
- // Get the commit hash for the index.html file
176
- const commitHash = await getFileCommitHash(token, owner, repo, indexHtmlPath, headers);
177
- if (!commitHash) {
178
- console.log(`⚠️ Could not get commit hash for index.html, continuing without it`);
179
- }
180
-
181
141
  const htmlContent = indexHtmlResponse.data;
182
142
 
183
143
  // Parse HTML using JSDOM
@@ -257,11 +217,6 @@ async function fetchAllTermsFromIndex(token, owner, repo, options = {}) {
257
217
  fs.writeFileSync(outputFilePath, JSON.stringify(result, null, 2));
258
218
  console.log(`✅ Saved ${terms.length} terms to ${outputFilePath}`);
259
219
 
260
- // Save to cache if enabled
261
- if (options.cache !== false) {
262
- saveToCache(cacheKey, result);
263
- }
264
-
265
220
  return result;
266
221
 
267
222
  } catch (error) {
@@ -4,19 +4,12 @@ const { matchTerm } = require('./matchTerm.js');
4
4
  const { addPath, getPath, getAllPaths } = require('../../config/paths');
5
5
  const path = require('path');
6
6
 
7
- // Directory to store cached files
7
+ // Directory to store fetched data files
8
8
  const CACHE_DIR = getPath('githubcache');
9
9
 
10
- async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, outputPathJS, outputPathJSTimeStamped, options) {
10
+ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, outputPathJS, outputPathJSTimeStamped) {
11
11
  try {
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
12
+ // Ensure the directory exists, so that we can store the fetched data
20
13
  if (!fs.existsSync(CACHE_DIR)) {
21
14
  fs.mkdirSync(CACHE_DIR, { recursive: true });
22
15
  }
@@ -51,12 +44,17 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
51
44
  const repoGroup = xrefsByRepo[repoKey];
52
45
  console.log(`Processing repository: ${repoKey} (${repoGroup.xtrefs.length} terms)`);
53
46
 
47
+ // Get the GitHub Pages URL from the first xtref in this repo group
48
+ const ghPageUrl = repoGroup.xtrefs[0]?.ghPageUrl;
49
+
54
50
  // First, fetch all terms from this repository
55
51
  const allTermsData = await fetchAllTermsFromIndex(
56
52
  GITHUB_API_TOKEN,
57
53
  repoGroup.owner,
58
54
  repoGroup.repo,
59
- options
55
+ {
56
+ ghPageUrl: ghPageUrl // Pass the GitHub Pages URL
57
+ }
60
58
  );
61
59
 
62
60
  if (!allTermsData) {