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.
- package/.github/copilot-instructions.md +2 -0
- package/assets/compiled/body.js +9 -7
- package/assets/compiled/head.css +6 -6
- package/assets/css/adjust-font-size.css +0 -4
- package/assets/css/download-pdf-docx.css +19 -20
- package/assets/css/header-navbar.css +0 -4
- package/assets/css/index.css +36 -0
- package/assets/css/terms-and-definitions.css +0 -1
- package/assets/css/validate-external-refs.css +198 -3
- package/assets/js/add-href-to-snapshot-link.js +11 -5
- package/assets/js/download-pdf-docx.js +20 -9
- package/assets/js/edit-term-buttons.js +71 -68
- package/assets/js/github-issues.js +27 -27
- package/assets/js/insert-irefs.js +1 -1
- package/assets/js/validate-external-refs.js +356 -7
- package/package.json +3 -3
- package/src/add-remove-xref-source.js +0 -5
- package/src/collect-external-references.test.js +98 -0
- package/src/install-from-boilerplate/boilerplate/gitignore +2 -2
- package/src/install-from-boilerplate/boilerplate/menu-wrapper.sh +19 -0
- package/src/install-from-boilerplate/boilerplate/specs.json +3 -6
- package/src/install-from-boilerplate/config-scripts-keys.js +1 -1
- package/src/install-from-boilerplate/config-system-files.js +1 -0
- package/src/install-from-boilerplate/custom-update.js +6 -3
- package/src/install-from-boilerplate/update-dependencies.js +105 -0
- package/src/pipeline/references/collect-external-references.js +12 -0
- package/src/pipeline/references/external-references-service.js +1 -1
- package/src/pipeline/rendering/render-spec-document.js +5 -1
- package/templates/template.html +43 -10
- package/src/health-check/destination-gitignore-checker.js +0 -414
- package/src/health-check/external-specs-checker.js +0 -287
- package/src/health-check/specs-configuration-checker.js +0 -387
- package/src/health-check/term-references-checker.js +0 -270
- package/src/health-check/terms-intro-checker.js +0 -82
- 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:
|
|
458
|
-
newContent:
|
|
459
|
-
similarity:
|
|
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:
|
|
536
|
-
newContent:
|
|
537
|
-
similarity:
|
|
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
|
+
"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": "^
|
|
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": "
|
|
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', () => {
|
|
@@ -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 ./
|
|
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",
|