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
@@ -53,6 +53,13 @@ const VALIDATOR_CONFIG = {
53
53
  */
54
54
  const fetchCache = new Map();
55
55
 
56
+ /**
57
+ * Collector for validation results to populate the changes report
58
+ * Each entry contains details about a changed or missing reference
59
+ * @type {Array<Object>}
60
+ */
61
+ const validationResults = [];
62
+
56
63
  /**
57
64
  * Normalizes HTML content for comparison by extracting text and normalizing whitespace
58
65
  * This helps compare definitions that might have minor formatting differences
@@ -391,6 +398,23 @@ function truncateText(text, maxLength) {
391
398
  return text.substring(0, maxLength) + '...';
392
399
  }
393
400
 
401
+ /**
402
+ * Records a validation result for the changes report
403
+ *
404
+ * @param {Object} result - The validation result to record
405
+ * @param {string} result.type - 'changed', 'missing', or 'error'
406
+ * @param {string} result.refType - 'xref' or 'tref'
407
+ * @param {string} result.term - The term name
408
+ * @param {string} result.externalSpec - The external specification name
409
+ * @param {string} [result.similarity] - Similarity percentage (for changed)
410
+ * @param {string} [result.oldContent] - Old cached content
411
+ * @param {string} [result.newContent] - New live content
412
+ * @param {HTMLElement} result.element - The DOM element
413
+ */
414
+ function recordValidationResult(result) {
415
+ validationResults.push(result);
416
+ }
417
+
394
418
  /**
395
419
  * Validates a single xref element against live data
396
420
  *
@@ -409,6 +433,13 @@ function validateXref(element, liveData, cachedXtref) {
409
433
  if (liveTerms === null) {
410
434
  const indicator = createIndicator('error');
411
435
  insertIndicatorAfterElement(element, indicator);
436
+ recordValidationResult({
437
+ type: 'error',
438
+ refType: 'xref',
439
+ term: cachedXtref.term,
440
+ externalSpec: cachedXtref.externalSpec,
441
+ element: element
442
+ });
412
443
  return;
413
444
  }
414
445
 
@@ -423,6 +454,13 @@ function validateXref(element, liveData, cachedXtref) {
423
454
  if (!liveTerm) {
424
455
  const indicator = createIndicator('missing');
425
456
  insertIndicatorAfterElement(element, indicator);
457
+ recordValidationResult({
458
+ type: 'missing',
459
+ refType: 'xref',
460
+ term: cachedXtref.term,
461
+ externalSpec: cachedXtref.externalSpec,
462
+ element: element
463
+ });
426
464
  return;
427
465
  }
428
466
 
@@ -453,12 +491,28 @@ function validateXref(element, liveData, cachedXtref) {
453
491
 
454
492
  // Only show changed indicator if similarity is below threshold (significant change)
455
493
  if (similarity < VALIDATOR_CONFIG.similarityThreshold) {
494
+ const oldContent = cachedXtref.content ? extractTextFromHtml(cachedXtref.content) : 'No cached content';
495
+ const newContent = liveTerm.content || 'No live content';
496
+ const similarityPercent = (similarity * 100).toFixed(1) + '%';
497
+
456
498
  const indicator = createIndicator('changed', {
457
- oldContent: cachedXtref.content ? extractTextFromHtml(cachedXtref.content) : 'No cached content',
458
- newContent: liveTerm.content || 'No live content',
459
- similarity: (similarity * 100).toFixed(1) + '%'
499
+ oldContent: oldContent,
500
+ newContent: newContent,
501
+ similarity: similarityPercent
460
502
  });
461
503
  insertIndicatorAfterElement(element, indicator);
504
+
505
+ // Record for the changes report
506
+ recordValidationResult({
507
+ type: 'changed',
508
+ refType: 'xref',
509
+ term: cachedXtref.term,
510
+ externalSpec: cachedXtref.externalSpec,
511
+ similarity: similarityPercent,
512
+ oldContent: oldContent,
513
+ newContent: newContent,
514
+ element: element
515
+ });
462
516
  return;
463
517
  }
464
518
 
@@ -487,6 +541,13 @@ function validateTref(element, liveData, cachedXtref) {
487
541
  if (liveTerms === null) {
488
542
  const indicator = createIndicator('error');
489
543
  insertIndicatorIntoTref(element, indicator);
544
+ recordValidationResult({
545
+ type: 'error',
546
+ refType: 'tref',
547
+ term: cachedXtref.term,
548
+ externalSpec: cachedXtref.externalSpec,
549
+ element: element
550
+ });
490
551
  return;
491
552
  }
492
553
 
@@ -501,6 +562,13 @@ function validateTref(element, liveData, cachedXtref) {
501
562
  if (!liveTerm) {
502
563
  const indicator = createIndicator('missing');
503
564
  insertIndicatorIntoTref(element, indicator);
565
+ recordValidationResult({
566
+ type: 'missing',
567
+ refType: 'tref',
568
+ term: cachedXtref.term,
569
+ externalSpec: cachedXtref.externalSpec,
570
+ element: element
571
+ });
504
572
  return;
505
573
  }
506
574
 
@@ -531,12 +599,28 @@ function validateTref(element, liveData, cachedXtref) {
531
599
 
532
600
  // Only show changed indicator if similarity is below threshold (significant change)
533
601
  if (similarity < VALIDATOR_CONFIG.similarityThreshold) {
602
+ const oldContent = cachedXtref.content ? extractTextFromHtml(cachedXtref.content) : 'No cached content';
603
+ const newContent = liveTerm.content || 'No live content';
604
+ const similarityPercent = (similarity * 100).toFixed(1) + '%';
605
+
534
606
  const indicator = createIndicator('changed', {
535
- oldContent: cachedXtref.content ? extractTextFromHtml(cachedXtref.content) : 'No cached content',
536
- newContent: liveTerm.content || 'No live content',
537
- similarity: (similarity * 100).toFixed(1) + '%'
607
+ oldContent: oldContent,
608
+ newContent: newContent,
609
+ similarity: similarityPercent
538
610
  });
539
611
  insertIndicatorIntoTref(element, indicator);
612
+
613
+ // Record for the changes report
614
+ recordValidationResult({
615
+ type: 'changed',
616
+ refType: 'tref',
617
+ term: cachedXtref.term,
618
+ externalSpec: cachedXtref.externalSpec,
619
+ similarity: similarityPercent,
620
+ oldContent: oldContent,
621
+ newContent: newContent,
622
+ element: element
623
+ });
540
624
  return;
541
625
  }
542
626
 
@@ -571,6 +655,8 @@ function insertIndicatorAfterElement(element, indicator) {
571
655
  return; // Already has an indicator
572
656
  }
573
657
  element.insertAdjacentElement('afterend', indicator);
658
+ // Attach click handler if this indicator has details
659
+ attachClickHandler(indicator);
574
660
  }
575
661
 
576
662
  /**
@@ -592,8 +678,48 @@ function insertIndicatorIntoTref(dtElement, indicator) {
592
678
  }
593
679
 
594
680
  termSpan.appendChild(indicator);
681
+ // Attach click handler if this indicator has details
682
+ attachClickHandler(indicator);
683
+ }
684
+
685
+ /**
686
+ * Attaches click handlers to validation indicators for toggling details visibility
687
+ * Closes details when clicking outside the indicator
688
+ *
689
+ * @param {HTMLElement} indicator - The indicator element with details
690
+ */
691
+ function attachClickHandler(indicator) {
692
+ if (!indicator.classList.contains('has-details')) {
693
+ return; // Only attach handlers to indicators with details
694
+ }
695
+
696
+ /**
697
+ * Toggle the active state when clicking the indicator
698
+ */
699
+ indicator.addEventListener('click', (event) => {
700
+ event.stopPropagation();
701
+ // Close all other open indicators
702
+ const allActive = document.querySelectorAll('.external-ref-validation-indicator.active');
703
+ for (const el of allActive) {
704
+ if (el !== indicator) {
705
+ el.classList.remove('active');
706
+ }
707
+ }
708
+ // Toggle this indicator
709
+ indicator.classList.toggle('active');
710
+ });
595
711
  }
596
712
 
713
+ /**
714
+ * Close all open indicator details when clicking anywhere on the document
715
+ */
716
+ document.addEventListener('click', () => {
717
+ const allActive = document.querySelectorAll('.external-ref-validation-indicator.active');
718
+ for (const indicator of allActive) {
719
+ indicator.classList.remove('active');
720
+ }
721
+ });
722
+
597
723
  /**
598
724
  * Collects all unique external specifications from the page
599
725
  * by examining xref and tref elements
@@ -724,16 +850,239 @@ async function validateExternalRefs() {
724
850
 
725
851
  console.log('[External Ref Validator] Validation complete');
726
852
 
853
+ // Populate the changes report in the slide-in menu
854
+ populateChangesReport();
855
+
727
856
  // Dispatch event to signal validation is complete
728
857
  document.dispatchEvent(new CustomEvent('external-refs-validated', {
729
858
  detail: {
730
859
  specsValidated: specs.size,
731
860
  xrefsValidated: xrefElements.length,
732
- trefsValidated: trefElements.length
861
+ trefsValidated: trefElements.length,
862
+ issuesFound: validationResults.length
733
863
  }
734
864
  }));
735
865
  }
736
866
 
867
+ /**
868
+ * Populates the external reference changes report in the slide-in settings menu
869
+ * Creates an accessible table showing all changed, missing, or error references
870
+ */
871
+ function populateChangesReport() {
872
+ const section = document.getElementById('external-ref-changes-section');
873
+ const container = document.getElementById('external-ref-changes-container');
874
+ const badge = document.getElementById('external-ref-changes-badge');
875
+ const status = document.getElementById('external-ref-validation-status');
876
+ const divider = document.getElementById('external-ref-changes-divider');
877
+
878
+ // If elements don't exist, skip (template may not include them)
879
+ if (!section || !container) {
880
+ console.log('[External Ref Validator] Changes report section not found in template');
881
+ return;
882
+ }
883
+
884
+ // Show the section
885
+ section.style.display = 'block';
886
+ if (divider) {
887
+ divider.style.display = 'block';
888
+ }
889
+
890
+ // Update status
891
+ if (status) {
892
+ if (validationResults.length === 0) {
893
+ status.innerHTML = '<i class="bi bi-check-circle-fill text-success me-1"></i>All external references are up to date';
894
+ } else {
895
+ status.innerHTML = `<i class="bi bi-exclamation-triangle-fill text-warning me-1"></i>${validationResults.length} issue(s) found`;
896
+ }
897
+ }
898
+
899
+ // Update badge
900
+ if (badge) {
901
+ badge.textContent = validationResults.length;
902
+ if (validationResults.length === 0) {
903
+ badge.classList.remove('bg-warning', 'bg-danger');
904
+ badge.classList.add('bg-success');
905
+ } else {
906
+ const hasErrors = validationResults.some(r => r.type === 'missing' || r.type === 'error');
907
+ badge.classList.remove('bg-success', hasErrors ? 'bg-warning' : 'bg-danger');
908
+ badge.classList.add(hasErrors ? 'bg-danger' : 'bg-warning');
909
+ }
910
+ }
911
+
912
+ // If no issues, show a simple message
913
+ if (validationResults.length === 0) {
914
+ container.innerHTML = '<p class="small text-success mb-0"><i class="bi bi-check-circle me-1"></i>No changes detected</p>';
915
+ return;
916
+ }
917
+
918
+ // Build an accessible table with the results
919
+ const table = buildChangesTable(validationResults);
920
+ container.innerHTML = '';
921
+ container.appendChild(table);
922
+ }
923
+
924
+ /**
925
+ * Builds an accessible HTML table from the validation results
926
+ *
927
+ * @param {Array<Object>} results - The validation results to display
928
+ * @returns {HTMLElement} - The table element
929
+ */
930
+ function buildChangesTable(results) {
931
+ const table = document.createElement('table');
932
+ table.classList.add('table', 'table-sm', 'table-hover', 'external-ref-changes-table');
933
+ table.setAttribute('role', 'table');
934
+ table.setAttribute('aria-label', 'External reference changes report');
935
+
936
+ // Table header
937
+ const thead = document.createElement('thead');
938
+ thead.innerHTML = `
939
+ <tr>
940
+ <th scope="col" class="col-status">Status</th>
941
+ <th scope="col" class="col-term">Term</th>
942
+ <th scope="col" class="col-spec">Source</th>
943
+ <th scope="col" class="col-similarity">Match</th>
944
+ <th scope="col" class="col-action">Action</th>
945
+ </tr>
946
+ `;
947
+ table.appendChild(thead);
948
+
949
+ // Table body
950
+ const tbody = document.createElement('tbody');
951
+
952
+ results.forEach((result, index) => {
953
+ const row = createResultRow(result, index);
954
+ tbody.appendChild(row);
955
+ });
956
+
957
+ table.appendChild(tbody);
958
+ return table;
959
+ }
960
+
961
+ /**
962
+ * Creates a table row for a single validation result
963
+ *
964
+ * @param {Object} result - The validation result
965
+ * @param {number} index - Row index for accessibility
966
+ * @returns {HTMLTableRowElement} - The table row element
967
+ */
968
+ function createResultRow(result, index) {
969
+ const row = document.createElement('tr');
970
+ row.classList.add(`result-${result.type}`);
971
+ row.setAttribute('data-result-index', index);
972
+
973
+ // Status cell with icon
974
+ const statusCell = document.createElement('td');
975
+ statusCell.classList.add('col-status');
976
+ statusCell.innerHTML = getStatusIcon(result.type);
977
+ statusCell.setAttribute('title', getStatusTitle(result.type));
978
+ row.appendChild(statusCell);
979
+
980
+ // Term cell
981
+ const termCell = document.createElement('td');
982
+ termCell.classList.add('col-term');
983
+ termCell.innerHTML = `<span class="term-name">${escapeHtml(result.term)}</span>`;
984
+ row.appendChild(termCell);
985
+
986
+ // Source/spec cell
987
+ const specCell = document.createElement('td');
988
+ specCell.classList.add('col-spec');
989
+ specCell.innerHTML = `<span class="spec-badge">${escapeHtml(result.externalSpec)}</span>`;
990
+ row.appendChild(specCell);
991
+
992
+ // Similarity cell (for changed items)
993
+ const similarityCell = document.createElement('td');
994
+ similarityCell.classList.add('col-similarity');
995
+ if (result.type === 'changed' && result.similarity) {
996
+ similarityCell.innerHTML = `<span class="similarity-value">${result.similarity}</span>`;
997
+ } else {
998
+ similarityCell.innerHTML = '<span class="similarity-na">—</span>';
999
+ }
1000
+ row.appendChild(similarityCell);
1001
+
1002
+ // Action cell with button to scroll to the element
1003
+ const actionCell = document.createElement('td');
1004
+ actionCell.classList.add('col-action');
1005
+ const goButton = document.createElement('button');
1006
+ goButton.classList.add('btn', 'btn-sm', 'btn-outline-primary', 'go-to-ref-btn');
1007
+ goButton.innerHTML = '<i class="bi bi-arrow-right-circle"></i>';
1008
+ goButton.setAttribute('title', 'Go to reference');
1009
+ goButton.setAttribute('aria-label', `Go to ${result.term} reference`);
1010
+ goButton.addEventListener('click', () => scrollToElement(result.element));
1011
+ actionCell.appendChild(goButton);
1012
+ row.appendChild(actionCell);
1013
+
1014
+ return row;
1015
+ }
1016
+
1017
+ /**
1018
+ * Returns the appropriate status icon HTML for a result type
1019
+ *
1020
+ * @param {string} type - The result type: 'changed', 'missing', or 'error'
1021
+ * @returns {string} - HTML string for the icon
1022
+ */
1023
+ function getStatusIcon(type) {
1024
+ switch (type) {
1025
+ case 'changed':
1026
+ return '<span class="status-icon status-changed" aria-label="Changed">🔄</span>';
1027
+ case 'missing':
1028
+ return '<span class="status-icon status-missing" aria-label="Missing">⚠️</span>';
1029
+ case 'error':
1030
+ return '<span class="status-icon status-error" aria-label="Error">❌</span>';
1031
+ default:
1032
+ return '<span class="status-icon" aria-label="Unknown">❓</span>';
1033
+ }
1034
+ }
1035
+
1036
+ /**
1037
+ * Returns the status title for a result type
1038
+ *
1039
+ * @param {string} type - The result type
1040
+ * @returns {string} - Human-readable status title
1041
+ */
1042
+ function getStatusTitle(type) {
1043
+ switch (type) {
1044
+ case 'changed':
1045
+ return 'Definition has changed in the external specification';
1046
+ case 'missing':
1047
+ return 'Term no longer exists in the external specification';
1048
+ case 'error':
1049
+ return 'Could not fetch external specification for validation';
1050
+ default:
1051
+ return 'Unknown status';
1052
+ }
1053
+ }
1054
+
1055
+ /**
1056
+ * Scrolls to the element in the document and highlights it temporarily
1057
+ * Also closes the offcanvas menu
1058
+ *
1059
+ * @param {HTMLElement} element - The element to scroll to
1060
+ */
1061
+ function scrollToElement(element) {
1062
+ if (!element) return;
1063
+
1064
+ // Close the offcanvas settings panel
1065
+ const offcanvas = document.getElementById('offcanvasSettings');
1066
+ if (offcanvas) {
1067
+ const bsOffcanvas = bootstrap.Offcanvas.getInstance(offcanvas);
1068
+ if (bsOffcanvas) {
1069
+ bsOffcanvas.hide();
1070
+ }
1071
+ }
1072
+
1073
+ // Small delay to let the offcanvas close
1074
+ setTimeout(() => {
1075
+ // Scroll to the element
1076
+ element.scrollIntoView({ behavior: 'smooth', block: 'center' });
1077
+
1078
+ // Add a temporary highlight effect
1079
+ element.classList.add('highlight-ref');
1080
+ setTimeout(() => {
1081
+ element.classList.remove('highlight-ref');
1082
+ }, 2000);
1083
+ }, 300);
1084
+ }
1085
+
737
1086
  /**
738
1087
  * Initialize the validator when DOM and trefs are ready
739
1088
  * We wait for the 'trefs-inserted' event to ensure all content is in place
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spec-up-t",
3
- "version": "1.6.3-beta.1",
3
+ "version": "1.6.4",
4
4
  "description": "Technical specification drafting tool that generates rich specification documents from markdown. Forked from https://github.com/decentralized-identity/spec-up by Daniel Buchner (https://github.com/csuwildcat)",
5
5
  "main": "./index",
6
6
  "repository": {
@@ -29,12 +29,12 @@
29
29
  "chalk": "^4.1.2",
30
30
  "cheerio": "^1.1.2",
31
31
  "dedent": "^1.5.3",
32
- "diff": "^7.0.0",
32
+ "diff": "^8.0.3",
33
33
  "docx": "^8.5.0",
34
34
  "dotenv": "^16.4.7",
35
35
  "find-pkg-dir": "^2.0.0",
36
36
  "fs-extra": "^11.3.0",
37
- "gulp": "4.0.2",
37
+ "gulp": "^5.0.1",
38
38
  "gulp-clean-css": "4.3.0",
39
39
  "gulp-concat": "2.6.1",
40
40
  "gulp-terser": "^2.1.0",
@@ -41,7 +41,6 @@ function askMode() {
41
41
  const questions = [
42
42
  'Enter URL of the repository: ',
43
43
  'Enter URL of the GitHub page: ',
44
- 'Enter the directory where the terms can be found: ',
45
44
  'Enter short unique name: ',
46
45
  ];
47
46
 
@@ -64,9 +63,6 @@ function askQuestion(index) {
64
63
  inputs.gh_page = answer;
65
64
  break;
66
65
  case 2:
67
- inputs.terms_dir = answer;
68
- break;
69
- case 3:
70
66
  inputs.external_spec = answer;
71
67
  break;
72
68
  }
@@ -92,7 +88,6 @@ function showReferences() {
92
88
  Logger.highlight(`Short name: ${spec.external_spec}`);
93
89
  Logger.info(`GitHub Page: ${spec.gh_page}`);
94
90
  Logger.info(`URL: ${spec.url}`);
95
- Logger.info(`Terms Directory: ${spec.terms_dir}`);
96
91
  Logger.separator();
97
92
  });
98
93
  rl.close();
@@ -155,6 +155,104 @@ That's all about these references.`,
155
155
  });
156
156
  });
157
157
 
158
+ describe('Cleanup of deleted files from sourceFiles array', () => {
159
+
160
+ // Test: Are deleted files removed from sourceFiles arrays?
161
+ it('should remove deleted files from sourceFiles array when files no longer exist', () => {
162
+ // Setup: Create an allXTrefs structure with multiple source files
163
+ const allXTrefs = {
164
+ xtrefs: [
165
+ {
166
+ externalSpec: 'ExtRef1',
167
+ term: 'greenhouse',
168
+ trefAliases: [],
169
+ xrefAliases: [],
170
+ sourceFiles: [
171
+ { file: 'composability.md', type: 'tref' },
172
+ { file: 'greenhouse.md', type: 'tref' },
173
+ { file: 'soil.md', type: 'xref' }
174
+ ]
175
+ },
176
+ {
177
+ externalSpec: 'ExtRef2',
178
+ term: 'propagation',
179
+ trefAliases: [],
180
+ xrefAliases: [],
181
+ sourceFiles: [
182
+ { file: 'composability.md', type: 'xref' },
183
+ { file: 'greenhouse.md', type: 'xref' },
184
+ { file: 'soil.md', type: 'xref' }
185
+ ]
186
+ }
187
+ ]
188
+ };
189
+
190
+ // Simulate: composability.md has been deleted
191
+ const fileContents = new Map([
192
+ ['greenhouse.md', '[[tref:ExtRef1,greenhouse]] [[xref:ExtRef2,propagation]]'],
193
+ ['soil.md', '[[xref:ExtRef1,greenhouse]] [[xref:ExtRef2,propagation]]']
194
+ ]);
195
+
196
+ // Filter out xtrefs that don't exist in any file (none should be removed)
197
+ allXTrefs.xtrefs = allXTrefs.xtrefs.filter(xtref =>
198
+ require('./pipeline/references/xtref-utils').isXTrefInAnyFile(xtref, fileContents)
199
+ );
200
+
201
+ // Clean up sourceFiles arrays to remove deleted files
202
+ allXTrefs.xtrefs.forEach(xtref => {
203
+ if (xtref.sourceFiles && Array.isArray(xtref.sourceFiles)) {
204
+ const currentFiles = Array.from(fileContents.keys());
205
+ xtref.sourceFiles = xtref.sourceFiles.filter(sf =>
206
+ currentFiles.includes(sf.file)
207
+ );
208
+ }
209
+ });
210
+
211
+ // Verify: composability.md should be removed from all sourceFiles arrays
212
+ expect(allXTrefs.xtrefs.length).toBe(2);
213
+
214
+ expect(allXTrefs.xtrefs[0].sourceFiles).toEqual([
215
+ { file: 'greenhouse.md', type: 'tref' },
216
+ { file: 'soil.md', type: 'xref' }
217
+ ]);
218
+
219
+ expect(allXTrefs.xtrefs[1].sourceFiles).toEqual([
220
+ { file: 'greenhouse.md', type: 'xref' },
221
+ { file: 'soil.md', type: 'xref' }
222
+ ]);
223
+ });
224
+
225
+ it('should handle case where all files are deleted for a term', () => {
226
+ // Setup: Create a term with only one source file
227
+ const allXTrefs = {
228
+ xtrefs: [
229
+ {
230
+ externalSpec: 'ExtRef1',
231
+ term: 'orphan-term',
232
+ trefAliases: [],
233
+ xrefAliases: [],
234
+ sourceFiles: [
235
+ { file: 'deleted-file.md', type: 'tref' }
236
+ ]
237
+ }
238
+ ]
239
+ };
240
+
241
+ // Simulate: All files deleted
242
+ const fileContents = new Map([
243
+ ['other-file.md', 'Some content without the orphan term']
244
+ ]);
245
+
246
+ // Filter out xtrefs that don't exist in any file
247
+ allXTrefs.xtrefs = allXTrefs.xtrefs.filter(xtref =>
248
+ require('./pipeline/references/xtref-utils').isXTrefInAnyFile(xtref, fileContents)
249
+ );
250
+
251
+ // This xtref should be completely removed since it doesn't exist in any file
252
+ expect(allXTrefs.xtrefs.length).toBe(0);
253
+ });
254
+ });
255
+
158
256
 
159
257
  // Tests for extracting and collecting external references from markdown
160
258
  describe('addNewXTrefsFromMarkdown', () => {
@@ -1,4 +1,4 @@
1
- node_modules
1
+ node_modules/
2
2
  *.log
3
3
  dist
4
4
  *.bak
@@ -7,5 +7,5 @@ dist
7
7
  .env
8
8
  coverage
9
9
  build
10
- .history
10
+ .history/
11
11
  /.cache/
@@ -0,0 +1,19 @@
1
+ #!/bin/bash
2
+
3
+ # This wrapper script checks if node_modules exists before attempting to run the menu
4
+ # This ensures users get a helpful error message if they forgot to run npm install
5
+
6
+ if [ ! -d "node_modules" ]; then
7
+ echo -e "\n⚠️ ERROR: node_modules directory not found."
8
+ echo -e " Please run 'npm install' first to install dependencies.\n"
9
+ exit 1
10
+ fi
11
+
12
+ if [ ! -d "node_modules/spec-up-t" ]; then
13
+ echo -e "\n⚠️ ERROR: spec-up-t package not found in node_modules."
14
+ echo -e " Please run 'npm install' to install dependencies.\n"
15
+ exit 1
16
+ fi
17
+
18
+ # Run the actual menu script
19
+ bash ./node_modules/spec-up-t/src/install-from-boilerplate/menu.sh "$@"
@@ -19,21 +19,18 @@
19
19
  "source": {
20
20
  "host": "github",
21
21
  "account": "trustoverip",
22
- "repo": "spec-up-t-starter-pack",
23
- "branch": "main"
22
+ "repo": "spec-up-t-starter-pack"
24
23
  },
25
24
  "external_specs": [
26
25
  {
27
26
  "external_spec": "ExtRef1",
28
27
  "gh_page": "https://trustoverip.github.io/spec-up-t-demo-external-ref-1/",
29
- "url": "https://github.com/trustoverip/spec-up-t-demo-external-ref-1",
30
- "terms_dir": "spec/terms-definitions"
28
+ "url": "https://github.com/trustoverip/spec-up-t-demo-external-ref-1"
31
29
  },
32
30
  {
33
31
  "external_spec": "ExtRef2",
34
32
  "gh_page": "https://trustoverip.github.io/spec-up-t-demo-external-ref-2/",
35
- "url": "https://github.com/trustoverip/spec-up-t-demo-external-ref-2",
36
- "terms_dir": "spec/terms-definitions"
33
+ "url": "https://github.com/trustoverip/spec-up-t-demo-external-ref-2"
37
34
  }
38
35
  ],
39
36
  "katex": false,
@@ -8,7 +8,7 @@ const configScriptsKeys = {
8
8
  "freeze": "node -e \"require('spec-up-t/src/freeze-spec-data.js')\"",
9
9
  "references": "node -e \"require('spec-up-t/src/pipeline/references/external-references-service.js')\"",
10
10
  "help": "cat ./node_modules/spec-up-t/src/install-from-boilerplate/help.txt",
11
- "menu": "bash ./node_modules/spec-up-t/src/install-from-boilerplate/menu.sh",
11
+ "menu": "bash ./menu-wrapper.sh",
12
12
  "addremovexrefsource": "node --no-warnings -e \"require('spec-up-t/src/add-remove-xref-source.js')\"",
13
13
  "configure": "node --no-warnings -e \"require('spec-up-t/src/configure.js')\"",
14
14
  "healthCheck": "node --no-warnings ./node_modules/spec-up-t/src/health-check.js",
@@ -1,6 +1,7 @@
1
1
  const systemFiles = [
2
2
  'README.md',
3
3
  '.env.example',
4
+ 'menu-wrapper.sh',
4
5
  '.github/workflows/menu.yml',
5
6
  '.github/workflows/set-gh-pages.yml',
6
7
  'assets/test.json',