spec-up-t 1.6.3-beta.1 → 1.6.4

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 (35) hide show
  1. package/.github/copilot-instructions.md +2 -0
  2. package/assets/compiled/body.js +9 -7
  3. package/assets/compiled/head.css +6 -6
  4. package/assets/css/adjust-font-size.css +0 -4
  5. package/assets/css/download-pdf-docx.css +19 -20
  6. package/assets/css/header-navbar.css +0 -4
  7. package/assets/css/index.css +36 -0
  8. package/assets/css/terms-and-definitions.css +0 -1
  9. package/assets/css/validate-external-refs.css +198 -3
  10. package/assets/js/add-href-to-snapshot-link.js +11 -5
  11. package/assets/js/download-pdf-docx.js +20 -9
  12. package/assets/js/edit-term-buttons.js +71 -68
  13. package/assets/js/github-issues.js +27 -27
  14. package/assets/js/insert-irefs.js +1 -1
  15. package/assets/js/validate-external-refs.js +356 -7
  16. package/package.json +3 -3
  17. package/src/add-remove-xref-source.js +0 -5
  18. package/src/collect-external-references.test.js +98 -0
  19. package/src/install-from-boilerplate/boilerplate/gitignore +2 -2
  20. package/src/install-from-boilerplate/boilerplate/menu-wrapper.sh +19 -0
  21. package/src/install-from-boilerplate/boilerplate/specs.json +3 -6
  22. package/src/install-from-boilerplate/config-scripts-keys.js +1 -1
  23. package/src/install-from-boilerplate/config-system-files.js +1 -0
  24. package/src/install-from-boilerplate/custom-update.js +6 -3
  25. package/src/install-from-boilerplate/update-dependencies.js +105 -0
  26. package/src/pipeline/references/collect-external-references.js +12 -0
  27. package/src/pipeline/references/external-references-service.js +1 -1
  28. package/src/pipeline/rendering/render-spec-document.js +5 -1
  29. package/templates/template.html +43 -10
  30. package/src/health-check/destination-gitignore-checker.js +0 -414
  31. package/src/health-check/external-specs-checker.js +0 -287
  32. package/src/health-check/specs-configuration-checker.js +0 -387
  33. package/src/health-check/term-references-checker.js +0 -270
  34. package/src/health-check/terms-intro-checker.js +0 -82
  35. package/src/health-check/tref-term-checker.js +0 -549
@@ -1,549 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const { shouldProcessFile } = require('../utils/file-filter');
4
- const Logger = require('../utils/logger');
5
-
6
- /**
7
- * Extracts the spec name and term from a tref tag at the beginning of a markdown file
8
- * @param {string} firstLine - The first line of a markdown file
9
- * @returns {Object|null} - Object containing repo and term, or null if not found
10
- */
11
- function extractTrefInfo(firstLine) {
12
- if (!firstLine.includes('[[tref:')) {
13
- return null;
14
- }
15
-
16
- try {
17
- // Extract content between [[tref: and ]]
18
- const trefMatch = firstLine.match(/\[\[tref:([^\]]+)\]\]/);
19
- if (!trefMatch?.length) {
20
- return null;
21
- }
22
-
23
- const trefContent = trefMatch[1]?.trim();
24
-
25
- // Split by the first comma
26
- const parts = trefContent.split(',');
27
- if (parts.length < 2) {
28
- return null;
29
- }
30
-
31
- // Extract repo and term
32
- const repo = parts[0].trim();
33
- const term = parts.slice(1).join(',').trim();
34
-
35
- return { repo, term };
36
- } catch (error) {
37
- Logger.error('Error extracting tref info:', error);
38
- return null;
39
- }
40
- }
41
-
42
- /**
43
- * Find all JSON cache files for repositories
44
- * @param {string} cacheDir - Directory containing the cached files
45
- * @returns {Array} - List of all JSON cache files
46
- */
47
- function findAllCacheFiles(cacheDir) {
48
- if (!fs.existsSync(cacheDir)) {
49
- return [];
50
- }
51
-
52
- try {
53
- const files = fs.readdirSync(cacheDir)
54
- .filter(file => file.endsWith('.json'))
55
- .map(file => path.join(cacheDir, file));
56
-
57
- return files;
58
- } catch (error) {
59
- Logger.error(`Error finding cache files:`, error);
60
- return [];
61
- }
62
- }
63
-
64
- /**
65
- * Checks if a term exists in a repository JSON cache file
66
- * @param {string} filePath - Path to the cached repository file
67
- * @param {string} term - Term to search for
68
- * @returns {boolean} - Whether the term exists in the file
69
- */
70
- function termExistsInRepo(filePath, term) {
71
- if (!fs.existsSync(filePath)) {
72
- return false;
73
- }
74
-
75
- try {
76
- const cacheData = JSON.parse(fs.readFileSync(filePath, 'utf8'));
77
-
78
- // Check if the file has a terms array
79
- if (!cacheData?.terms?.length) {
80
- Logger.warn(`Cache file ${filePath} has no terms array`);
81
- return false;
82
- }
83
-
84
- // Case-insensitive search for the term
85
- const termLower = term.toLowerCase();
86
-
87
- // Check each term in the terms array
88
- for (const termObj of cacheData.terms) {
89
- // First check the 'term' property if it exists
90
- if (termObj.term?.toLowerCase() === termLower) {
91
- return true;
92
- }
93
-
94
- // If there's a definition property, check if the term appears in it
95
- if (termObj.definition) {
96
- // Look for patterns like [[def: term]] or similar
97
- const defMatch = termObj.definition.match(/\[\[def:\s*([^\],]+)/i);
98
- if (defMatch?.[1]?.trim().toLowerCase() === termLower) {
99
- return true;
100
- }
101
- }
102
- }
103
-
104
- return false;
105
- } catch (error) {
106
- Logger.error(`Error checking if term exists in file ${filePath}:`, error);
107
- return false;
108
- }
109
- }
110
-
111
- /**
112
- * Find which repo a cache file belongs to
113
- * @param {string} filePath - Path to the cache file
114
- * @param {Array} externalSpecs - List of external specs
115
- * @returns {string|null} - The external_spec identifier or null
116
- */
117
- function findRepoForCacheFile(filePath, externalSpecs) {
118
- try {
119
- const cacheData = JSON.parse(fs.readFileSync(filePath, 'utf8'));
120
-
121
- if (!cacheData?.repository) {
122
- return null;
123
- }
124
-
125
- // Extract owner/repo from the repository field
126
- const repoPath = cacheData.repository;
127
-
128
- // Find matching external spec
129
- for (const spec of externalSpecs) {
130
- if (!spec.url) continue;
131
-
132
- // Extract owner/repo from the URL
133
- const match = spec.url.match(/github\.com\/([^/]+\/[^/]+)/i);
134
- if (match?.[1] && repoPath.includes(match[1])) {
135
- return spec.external_spec;
136
- }
137
- }
138
-
139
- return null;
140
- } catch (error) {
141
- Logger.error(`Error finding repository for cache file ${filePath}:`, error);
142
- return null; // Could not determine repository due to error reading or parsing cache file
143
- }
144
- }
145
-
146
- /**
147
- * Checks for specified term in all available external repos
148
- * @param {string} cacheDir - Directory containing the cached repository files
149
- * @param {Array} externalSpecs - List of external specs from specs.json
150
- * @param {string} currentRepo - The repository being checked (to exclude it)
151
- * @param {string} term - Term to search for
152
- * @returns {Array} - List of repositories where the term was found
153
- */
154
- function findTermInOtherRepos(cacheDir, externalSpecs, currentRepo, term) {
155
- const reposWithTerm = [];
156
-
157
- // Get all cache files
158
- const cacheFiles = findAllCacheFiles(cacheDir);
159
-
160
- for (const cacheFile of cacheFiles) {
161
- // Check which repo this cache file belongs to
162
- const repo = findRepoForCacheFile(cacheFile, externalSpecs);
163
-
164
- // Skip if we couldn't determine which repo this is or if it's the current repo
165
- if (!repo || repo === currentRepo) {
166
- continue;
167
- }
168
-
169
- // Check if the term exists in this repo
170
- if (termExistsInRepo(cacheFile, term)) {
171
- reposWithTerm.push(repo);
172
- }
173
- }
174
-
175
- return reposWithTerm;
176
- }
177
-
178
- /**
179
- * Find cache file for a specific external repo
180
- * @param {string} cacheDir - Cache directory
181
- * @param {Object} specConfig - External spec configuration
182
- * @returns {string|null} - Path to the cache file or null
183
- */
184
- function findCacheFileForRepo(cacheDir, specConfig) {
185
- if (!fs.existsSync(cacheDir) || !specConfig?.url) {
186
- return null;
187
- }
188
-
189
- try {
190
- // Extract owner and repo from URL
191
- const match = specConfig.url.match(/github\.com\/([^/]+)\/([^/]+)/i);
192
- if (!match?.[1] || !match?.[2]) {
193
- return null;
194
- }
195
-
196
- const owner = match[1];
197
- const repo = match[2];
198
-
199
- // Find all JSON files in the cache directory
200
- const files = fs.readdirSync(cacheDir)
201
- .filter(file => file.endsWith('.json'))
202
- .map(file => ({
203
- path: path.join(cacheDir, file),
204
- name: file
205
- }))
206
- // Sort by timestamp descending (assuming timestamp is at the beginning of filename)
207
- .sort((a, b) => {
208
- const timestampA = parseInt(a.name.split('-')[0] ?? '0', 10);
209
- const timestampB = parseInt(b.name.split('-')[0] ?? '0', 10);
210
- return timestampB - timestampA;
211
- });
212
-
213
- // Find the most recent cache file for this repo
214
- for (const file of files) {
215
- if (file.name.includes(owner) && file.name.includes(repo)) {
216
- return file.path;
217
- }
218
- }
219
-
220
- return null;
221
- } catch (error) {
222
- Logger.error(`Error finding cache file for repo:`, error);
223
- return null;
224
- }
225
- }
226
-
227
- /**
228
- * Get configuration and setup data from specs.json
229
- * @param {string} projectRoot - Root directory of the project
230
- * @returns {Object} - Object containing results, specs data, external specs and spec directories
231
- */
232
- async function getProjectConfiguration(projectRoot) {
233
- const results = [];
234
-
235
- // Path to the project's specs.json
236
- const specsPath = path.join(projectRoot, 'specs.json');
237
-
238
- // Check if specs.json exists
239
- if (!fs.existsSync(specsPath)) {
240
- return {
241
- results: [{
242
- name: 'Find specs.json file',
243
- success: false,
244
- details: 'specs.json file not found in project root'
245
- }],
246
- valid: false
247
- };
248
- }
249
-
250
- results.push({
251
- name: 'Find specs.json file',
252
- success: true,
253
- details: 'specs.json file found'
254
- });
255
-
256
- // Read specs.json to get the spec directory
257
- const specsContent = fs.readFileSync(specsPath, 'utf8');
258
- const specs = JSON.parse(specsContent);
259
-
260
- // Get the external specs
261
- if (!specs.specs?.length) {
262
- results.push({
263
- name: 'Find specs configuration',
264
- success: false,
265
- details: 'specs array not found in specs.json'
266
- });
267
- return { results, valid: false };
268
- }
269
-
270
- // Collect all external specs and spec directories
271
- const externalSpecs = [];
272
- const specDirs = [];
273
-
274
- specs.specs.forEach(spec => {
275
- if (spec.external_specs?.length) {
276
- externalSpecs.push(...spec.external_specs);
277
- }
278
-
279
- if (spec.spec_directory && spec.spec_terms_directory) {
280
- const termsDir = path.join(
281
- projectRoot,
282
- spec.spec_directory,
283
- spec.spec_terms_directory
284
- );
285
- specDirs.push(termsDir);
286
- }
287
- });
288
-
289
- if (externalSpecs.length === 0) {
290
- results.push({
291
- name: 'Find external specs',
292
- success: false,
293
- details: 'No external specs found in specs.json'
294
- });
295
- return { results, valid: false };
296
- }
297
-
298
- results.push({
299
- name: 'Find external specs',
300
- success: true,
301
- details: `Found ${externalSpecs.length} external specs`
302
- });
303
-
304
- if (specDirs.length === 0) {
305
- results.push({
306
- name: 'Find spec terms directories',
307
- success: false,
308
- details: 'No spec terms directories found'
309
- });
310
- return { results, valid: false };
311
- }
312
-
313
- results.push({
314
- name: 'Find spec terms directories',
315
- success: true,
316
- details: `Found ${specDirs.length} spec terms directories`
317
- });
318
-
319
- return {
320
- results,
321
- specs,
322
- externalSpecs,
323
- specDirs,
324
- valid: true,
325
- githubCacheDir: path.join(projectRoot, '.cache', 'github-cache')
326
- };
327
- }
328
-
329
- /**
330
- * Verify if a term exists in the specified repository
331
- * @param {Object} options - Options for verification
332
- * @param {string} options.githubCacheDir - Path to GitHub cache directory
333
- * @param {string} options.repo - Repository to check
334
- * @param {string} options.term - Term to find
335
- * @param {string} options.file - File name for reporting
336
- * @param {Array} options.externalSpecs - List of external specs
337
- * @returns {Object} - Object containing results and status
338
- */
339
- function verifyTermInRepo(options) {
340
- const { githubCacheDir, repo, term, file, externalSpecs } = options;
341
- const results = [];
342
-
343
- // Check if the referenced repo exists in external_specs
344
- const specConfig = externalSpecs.find(spec => spec.external_spec === repo);
345
- if (!specConfig) {
346
- results.push({
347
- name: `Check repo reference in ${file}`,
348
- success: false,
349
- details: `Referenced repo "${repo}" is not defined in external_specs`
350
- });
351
- return { results, status: 'invalid_repo' };
352
- }
353
-
354
- // Find the cache file for this repo
355
- const cacheFile = findCacheFileForRepo(githubCacheDir, specConfig);
356
-
357
- if (!cacheFile) {
358
- results.push({
359
- name: `Find cache for repo "${repo}" referenced in ${file}`,
360
- success: false,
361
- details: `No cache file found for repo "${repo}". Unable to verify if term "${term}" exists.`
362
- });
363
- return { results, status: 'no_cache' };
364
- }
365
-
366
- // Check if the term exists in the repo
367
- const termExists = termExistsInRepo(cacheFile, term);
368
-
369
- if (termExists) {
370
- results.push({
371
- name: `Term "${term}" in repo "${repo}" (${file})`,
372
- success: true,
373
- details: `Term "${term}" found in repo "${repo}"`
374
- });
375
- return { results, status: 'found' };
376
- }
377
-
378
- // Check if the term exists in other repos
379
- const otherRepos = findTermInOtherRepos(githubCacheDir, externalSpecs, repo, term);
380
-
381
- if (otherRepos.length > 0) {
382
- // Show as warning (partial success) instead of failure
383
- results.push({
384
- name: `Term "${term}" in repo "${repo}" (${file})`,
385
- success: 'partial', // Use 'partial' to indicate a warning
386
- details: `Warning: Term <code>${term}</code> NOT found in repo ${repo} but found in these repos: <code>${otherRepos.join(', ')}</code>. Consider updating the reference.`
387
- });
388
- return { results, status: 'found_elsewhere' };
389
- }
390
-
391
- results.push({
392
- name: `Term "${term}" in repo "${repo}" (${file})`,
393
- success: false,
394
- details: `Term <code>${term}</code> NOT found in repo <code>${repo}</code> and not found in any other external repos`
395
- });
396
- return { results, status: 'not_found' };
397
- }
398
-
399
- /**
400
- * Process a single markdown file to check for tref tags
401
- * @param {Object} options - Processing options
402
- * @param {string} options.filePath - Path to markdown file
403
- * @param {string} options.githubCacheDir - Path to GitHub cache directory
404
- * @param {Array} options.externalSpecs - List of external specs
405
- * @returns {Object} - Object containing results and counts
406
- */
407
- function processMarkdownFile(options) {
408
- const { filePath, githubCacheDir, externalSpecs } = options;
409
- const file = path.basename(filePath);
410
- const results = [];
411
- const counts = {
412
- filesWithTref: 0,
413
- validTerms: 0,
414
- invalidTerms: 0,
415
- termsFoundInOtherRepos: 0
416
- };
417
-
418
- try {
419
- const content = fs.readFileSync(filePath, 'utf8');
420
- const lines = content.split('\n');
421
- const firstLine = lines[0];
422
-
423
- if (!firstLine.includes('[[tref:')) {
424
- return { results, counts };
425
- }
426
-
427
- counts.filesWithTref = 1;
428
-
429
- // Extract repo and term information
430
- const trefInfo = extractTrefInfo(firstLine);
431
- if (!trefInfo) {
432
- results.push({
433
- name: `Parse tref in ${file}`,
434
- success: false,
435
- details: `Could not parse tref information from first line: "${firstLine}"`
436
- });
437
- return { results, counts };
438
- }
439
-
440
- const { repo, term } = trefInfo;
441
-
442
- // Verify the term
443
- const verification = verifyTermInRepo({
444
- githubCacheDir,
445
- repo,
446
- term,
447
- file,
448
- externalSpecs
449
- });
450
-
451
- results.push(...verification.results);
452
-
453
- // Update counts based on verification status
454
- if (verification.status === 'found') {
455
- counts.validTerms = 1;
456
- } else if (verification.status === 'found_elsewhere') {
457
- counts.termsFoundInOtherRepos = 1;
458
- } else if (verification.status === 'not_found') {
459
- counts.invalidTerms = 1;
460
- }
461
-
462
- } catch (error) {
463
- results.push({
464
- name: `Process file ${file}`,
465
- success: false,
466
- details: `Error processing file: ${error.message}`
467
- });
468
- }
469
-
470
- return { results, counts };
471
- }
472
-
473
- /**
474
- * Check if terms referenced via tref tags exist in the corresponding external repos
475
- * @param {string} projectRoot - Root directory of the project
476
- * @returns {Promise<Array>} - Array of check results
477
- */
478
- async function checkTrefTerms(projectRoot) {
479
- try {
480
- // Get project configuration
481
- const config = await getProjectConfiguration(projectRoot);
482
-
483
- // If configuration is invalid, return early with errors
484
- if (!config.valid) {
485
- return config.results;
486
- }
487
-
488
- const { results, specDirs, externalSpecs, githubCacheDir } = config;
489
-
490
- // Initialize counters
491
- let totalFiles = 0;
492
- let filesWithTref = 0;
493
- let validTerms = 0;
494
- let invalidTerms = 0;
495
- let termsFoundInOtherRepos = 0;
496
-
497
- // Process each spec directory
498
- for (const specDir of specDirs) {
499
- if (!fs.existsSync(specDir)) {
500
- continue;
501
- }
502
-
503
- // Get all markdown files
504
- const allFiles = fs.readdirSync(specDir);
505
- const markdownFiles = allFiles.filter(file => shouldProcessFile(file));
506
-
507
- totalFiles += markdownFiles.length;
508
-
509
- // Process each markdown file
510
- for (const file of markdownFiles) {
511
- const filePath = path.join(specDir, file);
512
- const fileResult = processMarkdownFile({
513
- filePath,
514
- githubCacheDir,
515
- externalSpecs
516
- });
517
-
518
- // Add results
519
- results.push(...fileResult.results);
520
-
521
- // Update counters
522
- filesWithTref += fileResult.counts.filesWithTref;
523
- validTerms += fileResult.counts.validTerms;
524
- invalidTerms += fileResult.counts.invalidTerms;
525
- termsFoundInOtherRepos += fileResult.counts.termsFoundInOtherRepos;
526
- }
527
- }
528
-
529
- // Add summary results
530
- results.push({
531
- name: 'Term reference validation summary',
532
- success: invalidTerms === 0,
533
- details: `Processed ${totalFiles} files, found ${filesWithTref} with tref tags. ${validTerms} terms found correctly, ${termsFoundInOtherRepos} terms found in alternative repos (warnings), ${invalidTerms} terms missing.`
534
- });
535
-
536
- return results;
537
- } catch (error) {
538
- Logger.error('Error checking tref terms:', error);
539
- return [{
540
- name: 'Term reference validation check',
541
- success: false,
542
- details: `Error: ${error.message}`
543
- }];
544
- }
545
- }
546
-
547
- module.exports = {
548
- checkTrefTerms
549
- };