mcprigor 1.3.1 → 1.5.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 (86) hide show
  1. package/README.md +19 -2
  2. package/dist/audit.d.ts +44 -0
  3. package/dist/audit.d.ts.map +1 -0
  4. package/dist/audit.js +157 -0
  5. package/dist/audit.js.map +1 -0
  6. package/dist/cli.js +283 -10
  7. package/dist/cli.js.map +1 -1
  8. package/dist/composition.d.ts +42 -0
  9. package/dist/composition.d.ts.map +1 -0
  10. package/dist/composition.js +130 -0
  11. package/dist/composition.js.map +1 -0
  12. package/dist/coverage.d.ts +33 -0
  13. package/dist/coverage.d.ts.map +1 -0
  14. package/dist/coverage.js +83 -0
  15. package/dist/coverage.js.map +1 -0
  16. package/dist/export.d.ts +20 -0
  17. package/dist/export.d.ts.map +1 -0
  18. package/dist/export.js +422 -0
  19. package/dist/export.js.map +1 -0
  20. package/dist/index.d.ts +5 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +5 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/language.js +1 -1
  25. package/dist/language.js.map +1 -1
  26. package/dist/loader.d.ts.map +1 -1
  27. package/dist/loader.js +4 -1
  28. package/dist/loader.js.map +1 -1
  29. package/dist/monitor.d.ts +22 -0
  30. package/dist/monitor.d.ts.map +1 -0
  31. package/dist/monitor.js +71 -0
  32. package/dist/monitor.js.map +1 -0
  33. package/dist/perf.d.ts +37 -0
  34. package/dist/perf.d.ts.map +1 -0
  35. package/dist/perf.js +90 -0
  36. package/dist/perf.js.map +1 -0
  37. package/dist/publish.d.ts +19 -0
  38. package/dist/publish.d.ts.map +1 -0
  39. package/dist/publish.js +58 -0
  40. package/dist/publish.js.map +1 -0
  41. package/dist/qa-language.d.ts.map +1 -1
  42. package/dist/qa-language.js +46 -1
  43. package/dist/qa-language.js.map +1 -1
  44. package/dist/reporters.d.ts +5 -2
  45. package/dist/reporters.d.ts.map +1 -1
  46. package/dist/reporters.js +26 -5
  47. package/dist/reporters.js.map +1 -1
  48. package/dist/runner.d.ts.map +1 -1
  49. package/dist/runner.js +31 -15
  50. package/dist/runner.js.map +1 -1
  51. package/dist/schema.d.ts +47 -0
  52. package/dist/schema.d.ts.map +1 -1
  53. package/dist/schema.js +10 -1
  54. package/dist/schema.js.map +1 -1
  55. package/dist/timeline.d.ts +18 -0
  56. package/dist/timeline.d.ts.map +1 -0
  57. package/dist/timeline.js +59 -0
  58. package/dist/timeline.js.map +1 -0
  59. package/dist/types.d.ts +19 -0
  60. package/dist/types.d.ts.map +1 -1
  61. package/dist/version.d.ts +1 -1
  62. package/dist/version.js +1 -1
  63. package/dist/workspace.d.ts.map +1 -1
  64. package/dist/workspace.js +73 -3
  65. package/dist/workspace.js.map +1 -1
  66. package/docs/CLI-REFERENCE.md +136 -1
  67. package/docs/COVERAGE.md +37 -0
  68. package/docs/ENGINEER-SETUP.md +1 -1
  69. package/docs/EVIDENCE.md +11 -0
  70. package/docs/GITHUB-ACTION.md +82 -0
  71. package/docs/LANGUAGE-SPEC.md +28 -7
  72. package/docs/MONITORING.md +62 -0
  73. package/docs/MULTI-SERVER-COMPOSITIONS.md +103 -0
  74. package/docs/PERFORMANCE-GOVERNANCE.md +85 -0
  75. package/docs/PLAIN-LANGUAGE-COOKBOOK.md +28 -0
  76. package/docs/PUBLISHING.md +53 -0
  77. package/docs/QA-WORKSPACE.md +5 -1
  78. package/docs/README.md +7 -0
  79. package/docs/SECURITY-AUDIT.md +77 -0
  80. package/editors/mcpr-language.json +2 -2
  81. package/editors/vscode/package.json +1 -1
  82. package/editors/vscode/syntaxes/mcpr.tmLanguage.json +2 -2
  83. package/package.json +1 -1
  84. package/workspace-assets/app.js +113 -1
  85. package/workspace-assets/index.html +17 -0
  86. package/workspace-assets/style.css +13 -2
@@ -1,4 +1,4 @@
1
- const state = { csrf: '', path: '', etag: '', dirty: false, files: [], selected: new Set(), run: null, history: [], tab: 'run', runSel: -1 };
1
+ const state = { csrf: '', path: '', etag: '', dirty: false, files: [], selected: new Set(), run: null, history: [], tab: 'run', runSel: -1, canPublish: false, publishing: false };
2
2
  const $ = id => document.getElementById(id);
3
3
 
4
4
  /* ---------- API ---------- */
@@ -14,6 +14,7 @@ async function api(path, options = {}) {
14
14
  async function start() {
15
15
  const boot = await api('/api/v1/bootstrap');
16
16
  state.csrf = boot.csrf;
17
+ state.canPublish = (boot.capabilities || []).includes('publish');
17
18
  $('version').textContent = `v${boot.version}`;
18
19
  $('workspace').textContent = `Folder: ${boot.root}`;
19
20
  $('connection').classList.add('ok');
@@ -341,6 +342,7 @@ async function run(mode, paths) {
341
342
  function renderRun() {
342
343
  const run = state.run;
343
344
  if (!run) return;
345
+ updateExportBar();
344
346
  const query = $('results-filter').value.trim();
345
347
  const list = $('run-list');
346
348
  list.replaceChildren();
@@ -548,3 +550,113 @@ $('refresh').onclick = () => refreshList().then(n => { if (!n && !state.path) sh
548
550
  $('filter').addEventListener('input', renderList);
549
551
  setEnabled(false);
550
552
  start().catch(show);
553
+
554
+ /* ---------- resizable panels ---------- */
555
+ (() => {
556
+ const mainEl = document.querySelector('main');
557
+ const KEY = 'mcprigor.panels';
558
+ const apply = (side, results) => {
559
+ if (side) mainEl.style.setProperty('--col-side', side);
560
+ if (results) mainEl.style.setProperty('--col-results', results);
561
+ };
562
+ try { const saved = JSON.parse(localStorage.getItem(KEY) || '{}'); apply(saved.side, saved.results); } catch {}
563
+ const save = () => {
564
+ try { localStorage.setItem(KEY, JSON.stringify({
565
+ side: mainEl.style.getPropertyValue('--col-side') || undefined,
566
+ results: mainEl.style.getPropertyValue('--col-results') || undefined,
567
+ })); } catch {}
568
+ };
569
+ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
570
+ const wire = (id, compute, keyboardStep) => {
571
+ const splitter = document.getElementById(id);
572
+ if (!splitter) return;
573
+ splitter.addEventListener('pointerdown', (event) => {
574
+ event.preventDefault();
575
+ splitter.setPointerCapture(event.pointerId);
576
+ splitter.classList.add('dragging');
577
+ document.body.classList.add('resizing');
578
+ const move = (ev) => compute(ev.clientX);
579
+ const stop = () => {
580
+ splitter.classList.remove('dragging');
581
+ document.body.classList.remove('resizing');
582
+ splitter.removeEventListener('pointermove', move);
583
+ splitter.removeEventListener('pointerup', stop);
584
+ splitter.removeEventListener('pointercancel', stop);
585
+ save();
586
+ };
587
+ splitter.addEventListener('pointermove', move);
588
+ splitter.addEventListener('pointerup', stop);
589
+ splitter.addEventListener('pointercancel', stop);
590
+ });
591
+ splitter.addEventListener('keydown', (event) => {
592
+ if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;
593
+ event.preventDefault();
594
+ keyboardStep(event.key === 'ArrowLeft' ? -16 : 16);
595
+ save();
596
+ });
597
+ splitter.addEventListener('dblclick', () => {
598
+ mainEl.style.removeProperty('--col-side');
599
+ mainEl.style.removeProperty('--col-results');
600
+ try { localStorage.removeItem(KEY); } catch {}
601
+ });
602
+ };
603
+ const sideWidth = () => document.querySelector('.sidebar').getBoundingClientRect().width;
604
+ const resultsWidth = () => document.querySelector('.results').getBoundingClientRect().width;
605
+ wire('split-left',
606
+ (x) => apply(`${clamp(x - mainEl.getBoundingClientRect().left, 160, 480)}px`, null),
607
+ (delta) => apply(`${clamp(sideWidth() + delta, 160, 480)}px`, null));
608
+ wire('split-right',
609
+ (x) => { const rect = mainEl.getBoundingClientRect(); apply(null, `${clamp(rect.right - x, 240, rect.width - 500)}px`); },
610
+ (delta) => { const rect = mainEl.getBoundingClientRect(); apply(null, `${clamp(resultsWidth() - delta, 240, rect.width - 500)}px`); });
611
+ })();
612
+
613
+ /* ---------- report exports ---------- */
614
+ function updateExportBar() {
615
+ const run = state.run;
616
+ const bar = $('export-run');
617
+ const index = state.runSel >= 0 ? state.runSel : 0;
618
+ const item = run && run.items && run.items[index];
619
+ const ready = run && run.mode === 'test' && item && item.status !== 'running' && item.tests;
620
+ bar.hidden = !ready;
621
+ const publish = $('publish-run');
622
+ const link = $('published-link');
623
+ publish.hidden = !(ready && state.canPublish);
624
+ publish.disabled = state.publishing;
625
+ publish.textContent = state.publishing ? 'Publishing…' : (item && item.publishedUrl ? 'Publish again' : 'Publish');
626
+ link.hidden = !(ready && item.publishedUrl);
627
+ if (item && item.publishedUrl) link.href = item.publishedUrl;
628
+ }
629
+ function exportRun(format) {
630
+ const run = state.run;
631
+ if (!run) return;
632
+ const index = state.runSel >= 0 ? state.runSel : 0;
633
+ window.open(`/api/v1/export/run?id=${encodeURIComponent(run.id)}&item=${index}&format=${format}`, '_blank');
634
+ }
635
+ async function publishRun() {
636
+ const run = state.run;
637
+ if (!run || state.publishing) return;
638
+ const index = state.runSel >= 0 ? state.runSel : 0;
639
+ state.publishing = true; updateExportBar();
640
+ try {
641
+ const value = await api('/api/v1/publish', { method: 'POST', body: JSON.stringify({ id: run.id, item: index }) });
642
+ const item = run.items[index];
643
+ if (item) item.publishedUrl = value.url;
644
+ window.open(value.url, '_blank', 'noopener');
645
+ } catch (error) {
646
+ toast(error.message || 'Publish failed', true);
647
+ } finally {
648
+ state.publishing = false; updateExportBar();
649
+ }
650
+ }
651
+ $('export-run-html').onclick = () => {
652
+ const run = state.run;
653
+ if (!run) return;
654
+ window.open(`/api/v1/report?id=${encodeURIComponent(run.id)}&item=${state.runSel >= 0 ? state.runSel : 0}`, '_blank');
655
+ };
656
+ $('publish-run').onclick = publishRun;
657
+ $('export-run-pdf').onclick = () => exportRun('pdf');
658
+ $('export-run-csv').onclick = () => exportRun('csv');
659
+ $('export-run-junit').onclick = () => exportRun('junit');
660
+ $('export-trends-pdf').onclick = () => window.open('/api/v1/export/trends?format=pdf', '_blank');
661
+ $('export-trends-csv').onclick = () => window.open('/api/v1/export/trends?format=csv', '_blank');
662
+ $('export-trends-raw').onclick = () => window.open('/api/v1/export/trends?format=raw-csv', '_blank');
@@ -41,6 +41,7 @@
41
41
  <a href="https://mcprigor.com/docs/plain-language-cookbook.html" target="_blank" rel="noreferrer">📖 Cookbook — copy-ready examples</a>
42
42
  </div>
43
43
  </aside>
44
+ <div class="splitter" id="split-left" role="separator" aria-orientation="vertical" aria-label="Resize file list" tabindex="0"></div>
44
45
 
45
46
  <section class="workbench">
46
47
  <div class="toolbar">
@@ -76,6 +77,7 @@
76
77
  <div id="diagnostics" class="diagnostics" role="status" aria-live="polite" hidden></div>
77
78
  </section>
78
79
 
80
+ <div class="splitter" id="split-right" role="separator" aria-orientation="vertical" aria-label="Resize results panel" tabindex="0"></div>
79
81
  <aside class="results">
80
82
  <div class="results-tabs" role="tablist">
81
83
  <button id="tab-run" class="tab active" role="tab" aria-selected="true">Run</button>
@@ -88,6 +90,15 @@
88
90
  <input id="results-filter" type="search" placeholder="Search tests and results…" aria-label="Search tests and results">
89
91
  </div>
90
92
  <div id="panel-run" class="panel" role="tabpanel">
93
+ <div id="export-run" class="export-bar" hidden>
94
+ <span class="export-label">Export report:</span>
95
+ <button id="export-run-pdf" class="ghost small" type="button">PDF</button>
96
+ <button id="export-run-csv" class="ghost small" type="button">CSV</button>
97
+ <button id="export-run-junit" class="ghost small" type="button">JUnit XML</button>
98
+ <button id="export-run-html" class="ghost small" type="button">HTML report</button>
99
+ <button id="publish-run" class="ghost small" type="button" hidden>Publish</button>
100
+ <a id="published-link" class="published-link" target="_blank" rel="noopener" hidden>View published ↗</a>
101
+ </div>
91
102
  <div id="run-list"></div>
92
103
  <pre id="output" class="output"><span class="muted">Nothing has run yet.
93
104
  Open a test file and click ▶ Run tests — or select several files
@@ -97,6 +108,12 @@ with their checkboxes and run them together.</span></pre>
97
108
  <div id="history-list" class="history-list"><p class="muted pad">No runs recorded yet. History appears after the first ▶ Run.</p></div>
98
109
  </div>
99
110
  <div id="panel-trends" class="panel" role="tabpanel" hidden>
111
+ <div class="export-bar">
112
+ <span class="export-label">Export trends:</span>
113
+ <button id="export-trends-pdf" class="ghost small" type="button">PDF</button>
114
+ <button id="export-trends-csv" class="ghost small" type="button">CSV</button>
115
+ <button id="export-trends-raw" class="ghost small" type="button">Raw history CSV</button>
116
+ </div>
100
117
  <div id="trends" class="trends"><p class="muted pad">Trends appear after a few runs.</p></div>
101
118
  </div>
102
119
  </aside>
@@ -27,10 +27,15 @@ header{display:flex;align-items:center;justify-content:space-between;padding:0 1
27
27
  footer{display:flex;align-items:center;justify-content:space-between;padding:0 18px;background:var(--bg-raised);border-top:1px solid var(--line);font-size:11.5px;color:var(--text-faint)}
28
28
 
29
29
  /* ---------- layout ---------- */
30
- main{display:grid;grid-template-columns:250px minmax(420px,1fr) minmax(320px,36%);min-height:0}
30
+ main{display:grid;grid-template-columns:var(--col-side,250px) 5px minmax(360px,1fr) 5px var(--col-results,minmax(320px,36%));min-height:0}
31
+ .splitter{cursor:col-resize;background:transparent;position:relative;z-index:5}
32
+ .splitter::after{content:"";position:absolute;inset:0 1px;background:var(--line);transition:background .15s}
33
+ .splitter:hover::after,.splitter.dragging::after{background:var(--accent)}
34
+ body.resizing{cursor:col-resize;user-select:none}
35
+ body.resizing iframe,body.resizing textarea{pointer-events:none}
31
36
 
32
37
  /* ---------- sidebar ---------- */
33
- .sidebar{display:flex;flex-direction:column;gap:10px;padding:14px 12px;background:var(--bg-raised);border-right:1px solid var(--line);min-height:0}
38
+ .sidebar{display:flex;flex-direction:column;gap:10px;padding:14px 12px;background:var(--bg-raised);min-height:0;overflow:hidden}
34
39
  .search-row{display:flex;gap:6px}
35
40
  #filter{flex:1;min-width:0;background:var(--bg-input);border:1px solid var(--line);border-radius:8px;color:var(--text);padding:7px 10px;font-size:13px;outline:none}
36
41
  #filter:focus{border-color:var(--accent-strong)}
@@ -223,3 +228,9 @@ button.small{padding:4px 8px;font-size:11.5px;color:var(--text-faint)}
223
228
  nav .suite-row button .rn{display:none;flex:none;font-size:10px;opacity:.6;padding:0 2px}
224
229
  nav .suite-row:hover button .rn{display:inline}
225
230
  nav .suite-row button .rn:hover{opacity:1}
231
+
232
+ .export-bar{display:flex;align-items:center;gap:6px;padding:8px 14px;border-bottom:1px solid var(--line-soft)}
233
+ .export-label{font-size:11px;color:var(--text-dim);margin-right:2px}
234
+ .export-bar .small{font-size:11px;padding:3px 10px}
235
+ .published-link{font-size:11px;margin-left:4px;color:var(--accent, #2563eb);text-decoration:none}
236
+ .published-link:hover{text-decoration:underline}