@ronkovic/aad 0.6.0 → 0.6.2
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/package.json +1 -1
- package/src/modules/dashboard/ui/dashboard.html +27 -27
- package/src/modules/git-workspace/__tests__/dependency-installer.test.ts +39 -0
- package/src/modules/git-workspace/dependency-installer.ts +10 -3
- package/src/modules/task-execution/__tests__/tester-verify.test.ts +122 -41
- package/src/modules/task-execution/index.ts +1 -1
- package/src/modules/task-execution/phases/tester-verify.ts +9 -76
package/package.json
CHANGED
|
@@ -586,14 +586,14 @@ tr:hover td {
|
|
|
586
586
|
<div class="log-entries" id="log-entries"></div>
|
|
587
587
|
</div>
|
|
588
588
|
|
|
589
|
-
<script type="module">class
|
|
589
|
+
<script type="module">class z{baseUrl;constructor(t){this.baseUrl=t}async getProgress(){let t=await fetch(`${this.baseUrl}/api/progress`);if(!t.ok)throw Error(`Failed to fetch progress: ${t.statusText}`);return t.json()}async getWorkers(){let t=await fetch(`${this.baseUrl}/api/workers`);if(!t.ok)throw Error(`Failed to fetch workers: ${t.statusText}`);return t.json()}async getTasks(){let t=await fetch(`${this.baseUrl}/api/tasks`);if(!t.ok)throw Error(`Failed to fetch tasks: ${t.statusText}`);return t.json()}async getGraph(){let t=await fetch(`${this.baseUrl}/api/graph`);if(!t.ok)throw Error(`Failed to fetch graph: ${t.statusText}`);return t.json()}async getTimeline(){let t=await fetch(`${this.baseUrl}/api/timeline`);if(!t.ok)throw Error(`Failed to fetch timeline: ${t.statusText}`);return t.json()}async getLogs(){let t=await fetch(`${this.baseUrl}/api/logs`);if(!t.ok)throw Error(`Failed to fetch logs: ${t.statusText}`);let i=await t.json();return Array.isArray(i)?i:i.entries||[]}async getTaskLogs(t){let i=await fetch(`${this.baseUrl}/api/tasks/${t}/logs`);if(!i.ok)throw Error(`Failed to fetch task logs: ${i.statusText}`);return i.json()}}class D{url;onEvent;eventSource=null;status="disconnected";reconnectAttempts=0;reconnectTimer=null;connectionChangeListeners=[];constructor(t,i){this.url=t;this.onEvent=i}connect(){if(this.eventSource)try{this.eventSource.close()}catch(t){}this.eventSource=new EventSource(this.url),this.eventSource.addEventListener("open",()=>{this.reconnectAttempts=0,this.setStatus("connected")}),this.eventSource.addEventListener("heartbeat",()=>{this.setStatus("connected")}),this.eventSource.addEventListener("message",(t)=>{try{let i=JSON.parse(t.data);this.onEvent(i)}catch(i){console.error("SSE parse error:",i)}}),this.eventSource.addEventListener("error",()=>{this.reconnectAttempts++;let t=this.reconnectAttempts>3?"disconnected":"reconnecting";if(this.setStatus(t),this.eventSource)this.eventSource.close();let i=Math.min(this.reconnectAttempts*3000,9000);this.reconnectTimer=setTimeout(()=>{this.connect()},i)})}disconnect(){if(this.reconnectTimer)clearTimeout(this.reconnectTimer),this.reconnectTimer=null;if(this.eventSource){try{this.eventSource.close()}catch(t){}this.eventSource=null}this.setStatus("disconnected")}isConnected(){return this.status==="connected"}getStatus(){return this.status}getReconnectAttempts(){return this.reconnectAttempts}onConnectionChange(t){this.connectionChangeListeners.push(t)}setStatus(t){if(this.status!==t)this.status=t,this.connectionChangeListeners.forEach((i)=>{try{i(t)}catch(d){console.error("Connection change listener error:",d)}})}}class L{state={progress:{pending:0,running:0,completed:0,failed:0,total:0},tasks:[],workers:[],graph:{nodes:[],edges:[]},timeline:{tasks:[]},logs:[],taskPhases:{}};listeners=[];getState(){return{...this.state}}updateProgress(t){this.state.progress=t,this.notify()}updateTasks(t){this.state.tasks=t,this.notify()}updateTask(t,i){let d=this.state.tasks.findIndex((n)=>n.taskId===t);if(d>=0)this.state.tasks[d]={...this.state.tasks[d],...i},this.notify()}updateWorkers(t){this.state.workers=t,this.notify()}updateGraph(t){this.state.graph=t,this.notify()}updateTimeline(t){this.state.timeline=t,this.notify()}addLog(t){if(this.state.logs.unshift(t),this.state.logs.length>200)this.state.logs=this.state.logs.slice(0,200);this.notify()}updateTaskPhase(t,i,d){this.state.taskPhases[t]={phase:i,status:d},this.notify()}subscribe(t){return this.listeners.push(t),()=>{let i=this.listeners.indexOf(t);if(i>=0)this.listeners.splice(i,1)}}notify(){let t=this.getState();this.listeners.forEach((i)=>{try{i(t)}catch(d){console.error("State listener error:",d)}})}}function j(t){let i=t.total||1,d=i>0?Math.round(t.completed/i*100):0,n=document.getElementById("pct-display"),a=document.getElementById("progress-fill"),e=document.getElementById("s-pending"),s=document.getElementById("s-running"),p=document.getElementById("s-completed"),o=document.getElementById("s-failed");if(n)n.textContent=`${d}%`;if(a)a.style.width=`${d}%`;if(e)e.textContent=String(t.pending||0);if(s)s.textContent=String(t.running||0);if(p)p.textContent=String(t.completed||0);if(o)o.textContent=String(t.failed||0)}function q(t){let i=document.createElement("div");return i.textContent=t,i.innerHTML}function P(t){let i=document.getElementById("worker-list"),d=document.getElementById("workers-count");if(!i)return;if(d)d.textContent=`(${t.length})`;if(!t.length){i.innerHTML='<div class="empty-msg">No workers</div>';return}i.innerHTML=t.map((n)=>{let a=n.currentTask?`<span class="worker-task">Task: ${q(String(n.currentTask))}</span>`:"";return`
|
|
590
590
|
<div class="worker-item">
|
|
591
|
-
<span class="worker-dot ${
|
|
592
|
-
<span class="worker-name">${
|
|
593
|
-
${
|
|
594
|
-
<span class="badge badge-${
|
|
591
|
+
<span class="worker-dot ${n.status}"></span>
|
|
592
|
+
<span class="worker-name">${q(String(n.id))}</span>
|
|
593
|
+
${a}
|
|
594
|
+
<span class="badge badge-${n.status}" style="margin-left:auto">${n.status}</span>
|
|
595
595
|
</div>
|
|
596
|
-
`}).join("")}function
|
|
596
|
+
`}).join("")}function $(t){let i=document.createElement("div");return i.textContent=t,i.innerHTML}class M{panelEl=null;overlayEl=null;constructor(){this.createPanel(),this.setupEventListeners()}createPanel(){this.overlayEl=document.createElement("div"),this.overlayEl.className="task-detail-overlay",this.overlayEl.style.cssText=`
|
|
597
597
|
display: none;
|
|
598
598
|
position: fixed;
|
|
599
599
|
top: 0;
|
|
@@ -618,13 +618,13 @@ tr:hover td {
|
|
|
618
618
|
overflow-y: auto;
|
|
619
619
|
z-index: 1001;
|
|
620
620
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
|
|
621
|
-
`,document.body.appendChild(this.overlayEl),document.body.appendChild(this.panelEl)}setupEventListeners(){this.overlayEl?.addEventListener("click",()=>this.close()),document.addEventListener("keydown",(t)=>{if(t.key==="Escape"&&this.isOpen())this.close()})}show(t,i){if(!this.panelEl||!this.overlayEl)return;let
|
|
621
|
+
`,document.body.appendChild(this.overlayEl),document.body.appendChild(this.panelEl)}setupEventListeners(){this.overlayEl?.addEventListener("click",()=>this.close()),document.addEventListener("keydown",(t)=>{if(t.key==="Escape"&&this.isOpen())this.close()})}show(t,i){if(!this.panelEl||!this.overlayEl)return;let d={red:"Red (Tests)",green:"Green (Implementation)",verify:"Verify",review:"Review",merge:"Merge"},n=i?`<div class="phase-info">
|
|
622
622
|
<strong>Current Phase:</strong>
|
|
623
623
|
<span class="activity-phase-${i.phase}">
|
|
624
|
-
${
|
|
624
|
+
${$(d[i.phase]||i.phase)}
|
|
625
625
|
(${i.status})
|
|
626
626
|
</span>
|
|
627
|
-
</div>`:"",
|
|
627
|
+
</div>`:"",a=t.startTime?new Date(t.startTime).toLocaleString():"—",e=t.endTime?new Date(t.endTime).toLocaleString():"—",s=this.calculateDuration(t);this.panelEl.innerHTML=`
|
|
628
628
|
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 20px;">
|
|
629
629
|
<h2 style="color: #4fc3f7; margin: 0; font-size: 18px;">Task Details</h2>
|
|
630
630
|
<button class="close-btn" style="background: #ef5350; color: white; border: none; border-radius: 4px; padding: 6px 12px; cursor: pointer; font-size: 14px; font-weight: 600;">Close</button>
|
|
@@ -633,12 +633,12 @@ tr:hover td {
|
|
|
633
633
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
|
634
634
|
<div>
|
|
635
635
|
<div style="color: #9e9e9e; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px;">Task ID</div>
|
|
636
|
-
<div style="font-family: monospace; font-size: 13px; color: #e0e0e0;">${
|
|
636
|
+
<div style="font-family: monospace; font-size: 13px; color: #e0e0e0;">${$(String(t.taskId))}</div>
|
|
637
637
|
</div>
|
|
638
638
|
|
|
639
639
|
<div>
|
|
640
640
|
<div style="color: #9e9e9e; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px;">Title</div>
|
|
641
|
-
<div style="font-size: 15px; color: #e0e0e0; font-weight: 500;">${
|
|
641
|
+
<div style="font-size: 15px; color: #e0e0e0; font-weight: 500;">${$(t.title||"—")}</div>
|
|
642
642
|
</div>
|
|
643
643
|
|
|
644
644
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
|
|
@@ -652,23 +652,23 @@ tr:hover td {
|
|
|
652
652
|
</div>
|
|
653
653
|
</div>
|
|
654
654
|
|
|
655
|
-
${
|
|
655
|
+
${n}
|
|
656
656
|
|
|
657
657
|
<div>
|
|
658
658
|
<div style="color: #9e9e9e; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px;">Dependencies</div>
|
|
659
659
|
<div style="font-size: 13px; color: #e0e0e0;">
|
|
660
|
-
${t.dependsOn&&t.dependsOn.length>0?t.dependsOn.map((
|
|
660
|
+
${t.dependsOn&&t.dependsOn.length>0?t.dependsOn.map((o)=>`<code style="background: #3a3a3a; padding: 2px 6px; border-radius: 3px; font-family: monospace; font-size: 11px;">${$(String(o))}</code>`).join(" "):"—"}
|
|
661
661
|
</div>
|
|
662
662
|
</div>
|
|
663
663
|
|
|
664
664
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
|
|
665
665
|
<div>
|
|
666
666
|
<div style="color: #9e9e9e; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px;">Start Time</div>
|
|
667
|
-
<div style="font-size: 12px; color: #e0e0e0;">${
|
|
667
|
+
<div style="font-size: 12px; color: #e0e0e0;">${a}</div>
|
|
668
668
|
</div>
|
|
669
669
|
<div>
|
|
670
670
|
<div style="color: #9e9e9e; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px;">End Time</div>
|
|
671
|
-
<div style="font-size: 12px; color: #e0e0e0;">${
|
|
671
|
+
<div style="font-size: 12px; color: #e0e0e0;">${e}</div>
|
|
672
672
|
</div>
|
|
673
673
|
</div>
|
|
674
674
|
|
|
@@ -677,20 +677,20 @@ tr:hover td {
|
|
|
677
677
|
<div style="font-size: 13px; color: #66bb6a; font-weight: 600;">${s}</div>
|
|
678
678
|
</div>`:""}
|
|
679
679
|
</div>
|
|
680
|
-
`;let
|
|
681
|
-
<tr data-task-id="${
|
|
682
|
-
<td style="font-family:monospace;font-size:11px">${
|
|
683
|
-
<td>${
|
|
684
|
-
<td><span class="badge badge-${
|
|
685
|
-
<td class="activity-cell">${
|
|
686
|
-
<td class="priority">${
|
|
680
|
+
`;let p=this.panelEl.querySelector(".close-btn");if(p)p.addEventListener("click",()=>this.close());this.overlayEl.style.display="block",this.panelEl.style.display="block"}close(){if(this.overlayEl)this.overlayEl.style.display="none";if(this.panelEl)this.panelEl.style.display="none"}isOpen(){return this.panelEl?.style.display==="block"}calculateDuration(t){if(!t.startTime)return null;let i=new Date(t.startTime).getTime(),n=(t.endTime?new Date(t.endTime).getTime():Date.now())-i,a=Math.floor(n/1000),e=Math.floor(a/60),s=Math.floor(e/60);if(s>0)return`${s}h ${e%60}m`;else if(e>0)return`${e}m ${a%60}s`;else return`${a}s`}}function h(t){let i=document.createElement("div");return i.textContent=t,i.innerHTML}function X(t,i,d){if(t.status==="running"){let n=i[t.taskId];if(n){let e={red:"Red (Tests)",green:"Green (Impl)",verify:"Verify",review:"Review",merge:"Merge"}[n.phase]||n.phase,s=n.status==="completed"?"✓ ":n.status==="failed"?"✗ ":"";return`<span class="activity-phase-${n.phase}">${s}${h(e)}</span>`}return'<span class="activity-phase-green">Running</span>'}else if(d==="connected")return'<span class="activity-connected">Connected</span>';else if(d==="reconnecting")return'<span class="activity-reconnecting">Reconnecting...</span>';else return'<span class="activity-disconnected">Disconnected</span>'}var Y=new M;function S(t,i,d){let n=document.getElementById("task-tbody"),a=document.getElementById("tasks-count");if(!n)return;if(a)a.textContent=`(${t.length})`;if(!t.length){n.innerHTML='<tr><td colspan="6" class="empty-msg">No tasks</td></tr>';return}n.innerHTML=t.map((e)=>{let s=(e.dependsOn||[]).map((o)=>h(String(o))).join(", ")||"—",p=X(e,i,d);return`
|
|
681
|
+
<tr data-task-id="${h(String(e.taskId))}" style="cursor: pointer;">
|
|
682
|
+
<td style="font-family:monospace;font-size:11px">${h(String(e.taskId))}</td>
|
|
683
|
+
<td>${h(e.title||"—")}</td>
|
|
684
|
+
<td><span class="badge badge-${e.status}">${e.status}</span></td>
|
|
685
|
+
<td class="activity-cell">${p}</td>
|
|
686
|
+
<td class="priority">${e.priority||0}</td>
|
|
687
687
|
<td class="deps">${s}</td>
|
|
688
688
|
</tr>
|
|
689
|
-
`}).join(""),
|
|
690
|
-
<span class="log-time">${
|
|
691
|
-
<span class="log-svc">${
|
|
692
|
-
<span>${
|
|
693
|
-
`,i)
|
|
689
|
+
`}).join(""),n.querySelectorAll("tr[data-task-id]").forEach((e)=>{e.addEventListener("click",()=>{let s=e.dataset.taskId,p=t.find((o)=>String(o.taskId)===s);if(p){let o=i[p.taskId];Y.show(p,o)}})})}var Z={pending:"#9e9e9e",running:"#4fc3f7",completed:"#66bb6a",failed:"#ef5350"};function H(t){let i=document.getElementById("graph-container");if(!i)return;if(i.innerHTML="",!t.nodes.length){i.innerHTML='<div class="empty-msg">No graph data</div>';return}let d=i.clientWidth||600,n=400,a=20,e=Math.ceil(Math.sqrt(t.nodes.length)),s=Math.ceil(t.nodes.length/e),p=d/(e+1),o=n/(s+1),g=new Map;t.nodes.forEach((f,y)=>{let T=y%e,l=Math.floor(y/e);g.set(f.id,{x:(T+1)*p,y:(l+1)*o})});let r=document.createElementNS("http://www.w3.org/2000/svg","svg");r.setAttribute("width",String(d)),r.setAttribute("height",String(n)),r.style.background="#1a1a1a";let v=document.createElementNS("http://www.w3.org/2000/svg","g");v.setAttribute("class","graph-edges"),t.edges.forEach((f)=>{let y=g.get(f.from),T=g.get(f.to);if(!y||!T)return;let l=document.createElementNS("http://www.w3.org/2000/svg","line");l.setAttribute("x1",String(y.x)),l.setAttribute("y1",String(y.y)),l.setAttribute("x2",String(T.x)),l.setAttribute("y2",String(T.y)),l.setAttribute("stroke","#424242"),l.setAttribute("stroke-width","2"),l.setAttribute("marker-end","url(#arrowhead)"),v.appendChild(l)});let N=document.createElementNS("http://www.w3.org/2000/svg","defs"),u=document.createElementNS("http://www.w3.org/2000/svg","marker");u.setAttribute("id","arrowhead"),u.setAttribute("markerWidth","10"),u.setAttribute("markerHeight","10"),u.setAttribute("refX","8"),u.setAttribute("refY","3"),u.setAttribute("orient","auto"),u.setAttribute("markerUnits","strokeWidth");let b=document.createElementNS("http://www.w3.org/2000/svg","polygon");b.setAttribute("points","0 0, 10 3, 0 6"),b.setAttribute("fill","#424242"),u.appendChild(b),N.appendChild(u),r.appendChild(N),r.appendChild(v);let E=document.createElementNS("http://www.w3.org/2000/svg","g");E.setAttribute("class","graph-nodes"),t.nodes.forEach((f)=>{let y=g.get(f.id);if(!y)return;let T=Z[f.status],l=document.createElementNS("http://www.w3.org/2000/svg","circle");l.setAttribute("cx",String(y.x)),l.setAttribute("cy",String(y.y)),l.setAttribute("r",String(a)),l.setAttribute("fill",T),l.setAttribute("stroke","#2d2d2d"),l.setAttribute("stroke-width","2");let c=document.createElementNS("http://www.w3.org/2000/svg","text");c.setAttribute("x",String(y.x)),c.setAttribute("y",String(y.y+4)),c.setAttribute("text-anchor","middle"),c.setAttribute("fill","#1a1a1a"),c.setAttribute("font-size","10"),c.setAttribute("font-weight","600"),c.textContent=f.id.slice(0,3);let W=document.createElementNS("http://www.w3.org/2000/svg","title");W.textContent=`${f.id} (${f.status})`,l.appendChild(W),E.appendChild(l),E.appendChild(c)}),r.appendChild(E),i.appendChild(r)}function J(t){let i=document.createElement("div");return i.textContent=t,i.innerHTML}class B{filters={info:!0,warn:!0,error:!0,service:"",search:""};autoScroll=!0;constructor(){this.setupEventListeners(),this.loadFiltersFromStorage()}setupEventListeners(){document.querySelectorAll(".log-filters input[data-level]").forEach((a)=>{a.addEventListener("change",()=>{let e=a,s=e.dataset.level;this.filters[s]=e.checked,this.saveFiltersToStorage()})});let t=document.getElementById("svc-filter");if(t)t.addEventListener("change",()=>{this.filters.service=t.value,this.saveFiltersToStorage()});let i=document.getElementById("log-search");if(i)i.addEventListener("input",()=>{this.filters.search=i.value,this.saveFiltersToStorage()});let d=document.getElementById("filter-reset");if(d)d.addEventListener("click",()=>{this.resetFilters()});let n=document.getElementById("log-entries");if(n)n.addEventListener("scroll",()=>{let a=n.scrollHeight-n.scrollTop<=n.clientHeight+10;this.autoScroll=a})}renderLog(t,i=!0){let d=document.getElementById("log-entries");if(!d)return;let n=document.createElement("div"),a=t.level||"info";n.className=`log-entry log-${a}`,n.dataset.level=a,n.dataset.service=t.service||"";let e=t.timestamp?new Date(t.timestamp).toLocaleTimeString():"";if(n.innerHTML=`
|
|
690
|
+
<span class="log-time">${e}</span>
|
|
691
|
+
<span class="log-svc">${J(t.service||"")}</span>
|
|
692
|
+
<span>${J(t.message||"")}</span>
|
|
693
|
+
`,i)d.insertBefore(n,d.firstChild);else d.appendChild(n);if(this.applyFilterToEntry(n),this.autoScroll&&i)d.scrollTop=0;this.updateLogCount()}renderAllLogs(t){let i=document.getElementById("log-entries");if(!i)return;i.innerHTML="",t.forEach((d)=>this.renderLog(d,!1)),this.updateLogCount(),this.updateServiceFilter(t)}applyFilterToEntry(t){let i=t.dataset.level,d=i?this.filters[i]:!1;if(d&&this.filters.service)d=t.dataset.service===this.filters.service;if(d&&this.filters.search){let n=this.filters.search.toLowerCase();d=(t.textContent?.toLowerCase()||"").includes(n)}t.classList.toggle("log-hidden",!d)}updateLogCount(){let t=document.getElementById("log-entries"),i=document.getElementById("log-count");if(!t||!i)return;let d=t.querySelectorAll(".log-entry").length,n=t.querySelectorAll(".log-entry:not(.log-hidden)").length;i.textContent=`(Showing ${n}/${d})`}updateServiceFilter(t){let i=document.getElementById("svc-filter");if(!i)return;let d=new Set(t.map((a)=>a.service).filter(Boolean)),n=i.value;if(i.innerHTML='<option value="">All Services</option>',d.forEach((a)=>{let e=document.createElement("option");e.value=a,e.textContent=a,i.appendChild(e)}),n)i.value=n}resetFilters(){this.filters={info:!0,warn:!0,error:!0,service:"",search:""},document.querySelectorAll(".log-filters input[data-level]").forEach((d)=>{d.checked=!0});let t=document.getElementById("svc-filter");if(t)t.value="";let i=document.getElementById("log-search");if(i)i.value="";this.saveFiltersToStorage(),this.reapplyFilters()}reapplyFilters(){let t=document.getElementById("log-entries");if(!t)return;t.querySelectorAll(".log-entry").forEach((i)=>{this.applyFilterToEntry(i)}),this.updateLogCount()}loadFiltersFromStorage(){try{let t=localStorage.getItem("aad-log-filters");if(t){let i=JSON.parse(t);this.filters={...this.filters,...i},document.querySelectorAll(".log-filters input[data-level]").forEach((a)=>{let e=a,s=e.dataset.level;e.checked=this.filters[s]!==!1});let d=document.getElementById("svc-filter");if(d&&this.filters.service)d.value=this.filters.service;let n=document.getElementById("log-search");if(n&&this.filters.search)n.value=this.filters.search}}catch(t){console.error("Failed to load filters:",t)}}saveFiltersToStorage(){try{localStorage.setItem("aad-log-filters",JSON.stringify(this.filters))}catch(t){console.error("Failed to save filters:",t)}this.reapplyFilters()}}var Q="http://localhost:7333",x=new z(Q),m=new L,O=new D(`${Q}/events/all`,K),R=new B;m.subscribe((t)=>{j(t.progress),P(t.workers),S(t.tasks,t.taskPhases,O.getStatus()),H(t.graph)});function K(t){switch(t.type){case"log:entry":m.addLog(t.entry),R.renderLog(t.entry,!0);break;case"progress:updated":m.updateProgress(t.state);break;case"execution:phase:started":m.updateTaskPhase(t.taskId,t.phase,"running");break;case"execution:phase:completed":m.updateTaskPhase(t.taskId,t.phase,"completed");break;case"execution:phase:failed":m.updateTaskPhase(t.taskId,t.phase,"failed");break;case"task:dispatched":case"task:completed":case"task:failed":if(t.task)m.updateTask(t.task.taskId,t.task);_(),U(),w();break;case"worker:idle":case"worker:busy":G();break;case"heartbeat":break}}async function V(){try{let[t,i,d,n,a,e]=await Promise.all([x.getProgress(),x.getWorkers(),x.getTasks(),x.getGraph(),x.getTimeline(),x.getLogs()]);m.updateProgress(t.progress),m.updateWorkers(i.workers),m.updateTasks(d.tasks),m.updateGraph(n),m.updateTimeline(a),e.forEach((s)=>m.addLog(s)),R.renderAllLogs(e)}catch(t){console.error("Failed to load initial data:",t)}}async function _(){try{let t=await x.getProgress();m.updateProgress(t.progress)}catch(t){console.error("Failed to refresh progress:",t)}}async function G(){try{let t=await x.getWorkers();m.updateWorkers(t.workers)}catch(t){console.error("Failed to refresh workers:",t)}}async function U(){try{let t=await x.getTimeline();m.updateTimeline(t)}catch(t){console.error("Failed to refresh timeline:",t)}}async function w(){try{let t=await x.getGraph();m.updateGraph(t)}catch(t){console.error("Failed to refresh graph:",t)}}V();O.connect();O.onConnectionChange((t)=>{console.log("SSE connection status:",t),S(m.getState().tasks,m.getState().taskPhases,t)});export{m as store,O as sseClient,x as apiClient};
|
|
694
694
|
</script>
|
|
695
695
|
</body>
|
|
696
696
|
</html>
|
|
@@ -94,3 +94,42 @@ describe("resolveAvailableCommand", () => {
|
|
|
94
94
|
expect(result).toBe("npx");
|
|
95
95
|
});
|
|
96
96
|
});
|
|
97
|
+
|
|
98
|
+
describe("installDependencies integration", () => {
|
|
99
|
+
test("integrates with resolveAvailableCommand", async () => {
|
|
100
|
+
// This test verifies that installDependencies calls resolveAvailableCommand
|
|
101
|
+
// Actual fallback behavior is tested in resolveAvailableCommand tests above
|
|
102
|
+
const { buildInstallCommand } = await import("../dependency-installer");
|
|
103
|
+
|
|
104
|
+
const workspace: WorkspaceInfo = {
|
|
105
|
+
path: "/tmp/test",
|
|
106
|
+
language: "javascript",
|
|
107
|
+
packageManager: "yarn",
|
|
108
|
+
framework: "react",
|
|
109
|
+
testFramework: "jest",
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const command = buildInstallCommand(workspace);
|
|
113
|
+
expect(command).toEqual(["yarn", "install", "--frozen-lockfile"]);
|
|
114
|
+
|
|
115
|
+
// The actual fallback is applied at runtime in installDependencies
|
|
116
|
+
// when yarn is not available in PATH, resolveAvailableCommand returns "npx"
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("constructs correct npx command when fallback is triggered", async () => {
|
|
120
|
+
// Test that when resolveAvailableCommand returns "npx",
|
|
121
|
+
// the spawn args preserve the original command name
|
|
122
|
+
const { resolveAvailableCommand } = await import("../dependency-installer");
|
|
123
|
+
|
|
124
|
+
// Simulate fallback scenario
|
|
125
|
+
const rawCmd = "yarn";
|
|
126
|
+
const args = ["install", "--frozen-lockfile"];
|
|
127
|
+
const resolved = await resolveAvailableCommand("nonexistent-binary-xyz123"); // Will return "npx"
|
|
128
|
+
|
|
129
|
+
// Construct spawn args as done in installDependencies
|
|
130
|
+
const spawnArgs = resolved === "npx" ? ["npx", rawCmd, ...args] : [resolved, ...args];
|
|
131
|
+
|
|
132
|
+
// Should be: ["npx", "yarn", "install", "--frozen-lockfile"]
|
|
133
|
+
expect(spawnArgs).toEqual(["npx", "yarn", "install", "--frozen-lockfile"]);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -76,11 +76,18 @@ export async function installDependencies(
|
|
|
76
76
|
return { success: true, output: "", duration: 0, skipped: true };
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
const [
|
|
80
|
-
|
|
79
|
+
const [rawCmd, ...args] = command;
|
|
80
|
+
|
|
81
|
+
// Resolve command with fallback (e.g., yarn → npx if yarn not found)
|
|
82
|
+
const cmd = await resolveAvailableCommand(rawCmd!);
|
|
83
|
+
|
|
84
|
+
// If resolved to npx, preserve original command name as first argument
|
|
85
|
+
const spawnArgs = cmd === "npx" ? ["npx", rawCmd!, ...args] : [cmd, ...args];
|
|
86
|
+
|
|
87
|
+
logger.info({ cmd: spawnArgs[0], args: spawnArgs.slice(1), cwd: workspace.path }, "Installing dependencies");
|
|
81
88
|
|
|
82
89
|
// Bun.spawn で実行(default-spawner.ts と同パターン)
|
|
83
|
-
const proc = Bun.spawn(
|
|
90
|
+
const proc = Bun.spawn(spawnArgs, {
|
|
84
91
|
cwd: workspace.path,
|
|
85
92
|
stdout: "pipe",
|
|
86
93
|
stderr: "pipe",
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { describe, test, expect } from "bun:test";
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
buildTestCommandWithFallback,
|
|
4
4
|
runTests,
|
|
5
5
|
type ProcessSpawner,
|
|
6
6
|
} from "../phases/tester-verify";
|
|
7
7
|
import type { WorkspaceInfo } from "@aad/shared/types";
|
|
8
8
|
|
|
9
|
-
describe("
|
|
10
|
-
test("builds bun test command", () => {
|
|
9
|
+
describe("buildTestCommandWithFallback", () => {
|
|
10
|
+
test("builds bun test command", async () => {
|
|
11
11
|
const workspace: WorkspaceInfo = {
|
|
12
12
|
path: "/path/to/workspace",
|
|
13
13
|
language: "typescript",
|
|
@@ -16,10 +16,10 @@ describe("buildTestCommand", () => {
|
|
|
16
16
|
testFramework: "bun-test",
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
expect(
|
|
19
|
+
expect(await buildTestCommandWithFallback(workspace)).toEqual(["bun", "test"]);
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
test("builds vitest command", () => {
|
|
22
|
+
test("builds vitest command", async () => {
|
|
23
23
|
const workspace: WorkspaceInfo = {
|
|
24
24
|
path: "/path/to/workspace",
|
|
25
25
|
language: "typescript",
|
|
@@ -28,10 +28,10 @@ describe("buildTestCommand", () => {
|
|
|
28
28
|
testFramework: "vitest",
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
expect(
|
|
31
|
+
expect(await buildTestCommandWithFallback(workspace)).toEqual(["npm", "run", "test"]);
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
test("builds jest command", () => {
|
|
34
|
+
test("builds jest command", async () => {
|
|
35
35
|
const workspace: WorkspaceInfo = {
|
|
36
36
|
path: "/path/to/workspace",
|
|
37
37
|
language: "javascript",
|
|
@@ -40,10 +40,10 @@ describe("buildTestCommand", () => {
|
|
|
40
40
|
testFramework: "jest",
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
expect(
|
|
43
|
+
expect(await buildTestCommandWithFallback(workspace)).toEqual(["yarn", "test"]);
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
test("builds pytest command", () => {
|
|
46
|
+
test("builds pytest command", async () => {
|
|
47
47
|
const workspace: WorkspaceInfo = {
|
|
48
48
|
path: "/path/to/workspace",
|
|
49
49
|
language: "python",
|
|
@@ -52,10 +52,10 @@ describe("buildTestCommand", () => {
|
|
|
52
52
|
testFramework: "pytest",
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
-
expect(
|
|
55
|
+
expect(await buildTestCommandWithFallback(workspace)).toEqual(["pytest", "-v"]);
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
test("builds go test command", () => {
|
|
58
|
+
test("builds go test command", async () => {
|
|
59
59
|
const workspace: WorkspaceInfo = {
|
|
60
60
|
path: "/path/to/workspace",
|
|
61
61
|
language: "go",
|
|
@@ -64,10 +64,10 @@ describe("buildTestCommand", () => {
|
|
|
64
64
|
testFramework: "go-test",
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
-
expect(
|
|
67
|
+
expect(await buildTestCommandWithFallback(workspace)).toEqual(["go", "test", "./..."]);
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
-
test("builds cargo test command", () => {
|
|
70
|
+
test("builds cargo test command", async () => {
|
|
71
71
|
const workspace: WorkspaceInfo = {
|
|
72
72
|
path: "/path/to/workspace",
|
|
73
73
|
language: "rust",
|
|
@@ -76,10 +76,10 @@ describe("buildTestCommand", () => {
|
|
|
76
76
|
testFramework: "cargo",
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
-
expect(
|
|
79
|
+
expect(await buildTestCommandWithFallback(workspace)).toEqual(["cargo", "test"]);
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
-
test("builds vitest command with yarn", () => {
|
|
82
|
+
test("builds vitest command with yarn", async () => {
|
|
83
83
|
const workspace: WorkspaceInfo = {
|
|
84
84
|
path: "/path",
|
|
85
85
|
language: "typescript",
|
|
@@ -87,10 +87,10 @@ describe("buildTestCommand", () => {
|
|
|
87
87
|
framework: "vite",
|
|
88
88
|
testFramework: "vitest",
|
|
89
89
|
};
|
|
90
|
-
expect(
|
|
90
|
+
expect(await buildTestCommandWithFallback(workspace)).toEqual(["yarn", "test"]);
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
-
test("builds vitest command with pnpm", () => {
|
|
93
|
+
test("builds vitest command with pnpm", async () => {
|
|
94
94
|
const workspace: WorkspaceInfo = {
|
|
95
95
|
path: "/path",
|
|
96
96
|
language: "typescript",
|
|
@@ -98,10 +98,10 @@ describe("buildTestCommand", () => {
|
|
|
98
98
|
framework: "vite",
|
|
99
99
|
testFramework: "vitest",
|
|
100
100
|
};
|
|
101
|
-
expect(
|
|
101
|
+
expect(await buildTestCommandWithFallback(workspace)).toEqual(["pnpm", "test"]);
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
-
test("builds vitest command with default (npx)", () => {
|
|
104
|
+
test("builds vitest command with default (npx)", async () => {
|
|
105
105
|
const workspace: WorkspaceInfo = {
|
|
106
106
|
path: "/path",
|
|
107
107
|
language: "typescript",
|
|
@@ -109,10 +109,10 @@ describe("buildTestCommand", () => {
|
|
|
109
109
|
framework: "vite",
|
|
110
110
|
testFramework: "vitest",
|
|
111
111
|
};
|
|
112
|
-
expect(
|
|
112
|
+
expect(await buildTestCommandWithFallback(workspace)).toEqual(["npx", "vitest", "run"]);
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
-
test("builds jest command with npm", () => {
|
|
115
|
+
test("builds jest command with npm", async () => {
|
|
116
116
|
const workspace: WorkspaceInfo = {
|
|
117
117
|
path: "/path",
|
|
118
118
|
language: "javascript",
|
|
@@ -120,10 +120,10 @@ describe("buildTestCommand", () => {
|
|
|
120
120
|
framework: "react",
|
|
121
121
|
testFramework: "jest",
|
|
122
122
|
};
|
|
123
|
-
expect(
|
|
123
|
+
expect(await buildTestCommandWithFallback(workspace)).toEqual(["npm", "test"]);
|
|
124
124
|
});
|
|
125
125
|
|
|
126
|
-
test("builds jest command with pnpm", () => {
|
|
126
|
+
test("builds jest command with pnpm", async () => {
|
|
127
127
|
const workspace: WorkspaceInfo = {
|
|
128
128
|
path: "/path",
|
|
129
129
|
language: "javascript",
|
|
@@ -131,10 +131,10 @@ describe("buildTestCommand", () => {
|
|
|
131
131
|
framework: "react",
|
|
132
132
|
testFramework: "jest",
|
|
133
133
|
};
|
|
134
|
-
expect(
|
|
134
|
+
expect(await buildTestCommandWithFallback(workspace)).toEqual(["pnpm", "test"]);
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
-
test("builds jest command with default (npx)", () => {
|
|
137
|
+
test("builds jest command with default (npx)", async () => {
|
|
138
138
|
const workspace: WorkspaceInfo = {
|
|
139
139
|
path: "/path",
|
|
140
140
|
language: "javascript",
|
|
@@ -142,10 +142,10 @@ describe("buildTestCommand", () => {
|
|
|
142
142
|
framework: "react",
|
|
143
143
|
testFramework: "jest",
|
|
144
144
|
};
|
|
145
|
-
expect(
|
|
145
|
+
expect(await buildTestCommandWithFallback(workspace)).toEqual(["npx", "jest"]);
|
|
146
146
|
});
|
|
147
147
|
|
|
148
|
-
test("builds mocha command with npm", () => {
|
|
148
|
+
test("builds mocha command with npm", async () => {
|
|
149
149
|
const workspace: WorkspaceInfo = {
|
|
150
150
|
path: "/path",
|
|
151
151
|
language: "javascript",
|
|
@@ -153,10 +153,10 @@ describe("buildTestCommand", () => {
|
|
|
153
153
|
framework: "express",
|
|
154
154
|
testFramework: "mocha",
|
|
155
155
|
};
|
|
156
|
-
expect(
|
|
156
|
+
expect(await buildTestCommandWithFallback(workspace)).toEqual(["npm", "test"]);
|
|
157
157
|
});
|
|
158
158
|
|
|
159
|
-
test("builds mocha command with yarn", () => {
|
|
159
|
+
test("builds mocha command with yarn", async () => {
|
|
160
160
|
const workspace: WorkspaceInfo = {
|
|
161
161
|
path: "/path",
|
|
162
162
|
language: "javascript",
|
|
@@ -164,10 +164,10 @@ describe("buildTestCommand", () => {
|
|
|
164
164
|
framework: "express",
|
|
165
165
|
testFramework: "mocha",
|
|
166
166
|
};
|
|
167
|
-
expect(
|
|
167
|
+
expect(await buildTestCommandWithFallback(workspace)).toEqual(["yarn", "test"]);
|
|
168
168
|
});
|
|
169
169
|
|
|
170
|
-
test("builds mocha command with default (npx)", () => {
|
|
170
|
+
test("builds mocha command with default (npx)", async () => {
|
|
171
171
|
const workspace: WorkspaceInfo = {
|
|
172
172
|
path: "/path",
|
|
173
173
|
language: "javascript",
|
|
@@ -175,10 +175,10 @@ describe("buildTestCommand", () => {
|
|
|
175
175
|
framework: "express",
|
|
176
176
|
testFramework: "mocha",
|
|
177
177
|
};
|
|
178
|
-
expect(
|
|
178
|
+
expect(await buildTestCommandWithFallback(workspace)).toEqual(["npx", "mocha"]);
|
|
179
179
|
});
|
|
180
180
|
|
|
181
|
-
test("builds maven test command", () => {
|
|
181
|
+
test("builds maven test command", async () => {
|
|
182
182
|
const workspace: WorkspaceInfo = {
|
|
183
183
|
path: "/path",
|
|
184
184
|
language: "java",
|
|
@@ -186,10 +186,10 @@ describe("buildTestCommand", () => {
|
|
|
186
186
|
framework: "spring",
|
|
187
187
|
testFramework: "maven",
|
|
188
188
|
};
|
|
189
|
-
expect(
|
|
189
|
+
expect(await buildTestCommandWithFallback(workspace)).toEqual(["mvn", "test"]);
|
|
190
190
|
});
|
|
191
191
|
|
|
192
|
-
test("builds gradle test command", () => {
|
|
192
|
+
test("builds gradle test command", async () => {
|
|
193
193
|
const workspace: WorkspaceInfo = {
|
|
194
194
|
path: "/path",
|
|
195
195
|
language: "java",
|
|
@@ -197,10 +197,10 @@ describe("buildTestCommand", () => {
|
|
|
197
197
|
framework: "spring",
|
|
198
198
|
testFramework: "gradle",
|
|
199
199
|
};
|
|
200
|
-
expect(
|
|
200
|
+
expect(await buildTestCommandWithFallback(workspace)).toEqual(["./gradlew", "test"]);
|
|
201
201
|
});
|
|
202
202
|
|
|
203
|
-
test("returns fallback for unknown test framework", () => {
|
|
203
|
+
test("returns fallback for unknown test framework with npm", async () => {
|
|
204
204
|
const workspace: WorkspaceInfo = {
|
|
205
205
|
path: "/path/to/workspace",
|
|
206
206
|
language: "unknown",
|
|
@@ -209,8 +209,89 @@ describe("buildTestCommand", () => {
|
|
|
209
209
|
testFramework: "unknown",
|
|
210
210
|
};
|
|
211
211
|
|
|
212
|
-
|
|
213
|
-
|
|
212
|
+
expect(await buildTestCommandWithFallback(workspace)).toEqual(["npm", "test"]);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("returns fallback for unknown test framework with bun", async () => {
|
|
216
|
+
const workspace: WorkspaceInfo = {
|
|
217
|
+
path: "/path/to/workspace",
|
|
218
|
+
language: "unknown",
|
|
219
|
+
packageManager: "bun",
|
|
220
|
+
framework: "unknown",
|
|
221
|
+
testFramework: "unknown",
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
expect(await buildTestCommandWithFallback(workspace)).toEqual(["bun", "test"]);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("returns fallback for unknown test framework with yarn", async () => {
|
|
228
|
+
const workspace: WorkspaceInfo = {
|
|
229
|
+
path: "/path/to/workspace",
|
|
230
|
+
language: "unknown",
|
|
231
|
+
packageManager: "yarn",
|
|
232
|
+
framework: "unknown",
|
|
233
|
+
testFramework: "unknown",
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
expect(await buildTestCommandWithFallback(workspace)).toEqual(["yarn", "test"]);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("returns fallback for unknown test framework with pnpm", async () => {
|
|
240
|
+
const workspace: WorkspaceInfo = {
|
|
241
|
+
path: "/path/to/workspace",
|
|
242
|
+
language: "unknown",
|
|
243
|
+
packageManager: "pnpm",
|
|
244
|
+
framework: "unknown",
|
|
245
|
+
testFramework: "unknown",
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
expect(await buildTestCommandWithFallback(workspace)).toEqual(["pnpm", "test"]);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("builds unknown test with yarn fallback when yarn is unavailable", async () => {
|
|
252
|
+
const workspace: WorkspaceInfo = {
|
|
253
|
+
path: "/path",
|
|
254
|
+
language: "unknown",
|
|
255
|
+
packageManager: "yarn",
|
|
256
|
+
framework: "unknown",
|
|
257
|
+
testFramework: "unknown",
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const originalWhich = Bun.which;
|
|
261
|
+
Bun.which = (cmd: string) => {
|
|
262
|
+
if (cmd === "yarn") return null;
|
|
263
|
+
return originalWhich(cmd);
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const result = await buildTestCommandWithFallback(workspace);
|
|
268
|
+
expect(result).toEqual(["npx", "yarn", "test"]);
|
|
269
|
+
} finally {
|
|
270
|
+
Bun.which = originalWhich;
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test("builds unknown test with pnpm fallback when pnpm is unavailable", async () => {
|
|
275
|
+
const workspace: WorkspaceInfo = {
|
|
276
|
+
path: "/path",
|
|
277
|
+
language: "unknown",
|
|
278
|
+
packageManager: "pnpm",
|
|
279
|
+
framework: "unknown",
|
|
280
|
+
testFramework: "unknown",
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const originalWhich = Bun.which;
|
|
284
|
+
Bun.which = (cmd: string) => {
|
|
285
|
+
if (cmd === "pnpm") return null;
|
|
286
|
+
return originalWhich(cmd);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
const result = await buildTestCommandWithFallback(workspace);
|
|
291
|
+
expect(result).toEqual(["npx", "pnpm", "test"]);
|
|
292
|
+
} finally {
|
|
293
|
+
Bun.which = originalWhich;
|
|
294
|
+
}
|
|
214
295
|
});
|
|
215
296
|
|
|
216
297
|
test("builds vitest with fallback when yarn is unavailable", async () => {
|
|
@@ -230,7 +311,7 @@ describe("buildTestCommand", () => {
|
|
|
230
311
|
};
|
|
231
312
|
|
|
232
313
|
try {
|
|
233
|
-
const result = await
|
|
314
|
+
const result = await buildTestCommandWithFallback(workspace);
|
|
234
315
|
expect(result).toEqual(["npx", "yarn", "test"]);
|
|
235
316
|
} finally {
|
|
236
317
|
Bun.which = originalWhich;
|
|
@@ -253,7 +334,7 @@ describe("buildTestCommand", () => {
|
|
|
253
334
|
};
|
|
254
335
|
|
|
255
336
|
try {
|
|
256
|
-
const result = await
|
|
337
|
+
const result = await buildTestCommandWithFallback(workspace);
|
|
257
338
|
expect(result).toEqual(["npx", "pnpm", "test"]);
|
|
258
339
|
} finally {
|
|
259
340
|
Bun.which = originalWhich;
|
|
@@ -14,7 +14,7 @@ export {
|
|
|
14
14
|
} from "./phases/implementer-green";
|
|
15
15
|
export type { ImplementerGreenOptions } from "./phases/implementer-green";
|
|
16
16
|
|
|
17
|
-
export { runTests,
|
|
17
|
+
export { runTests, buildTestCommandWithFallback } from "./phases/tester-verify";
|
|
18
18
|
export type {
|
|
19
19
|
TestResult,
|
|
20
20
|
ProcessSpawner,
|
|
@@ -94,8 +94,14 @@ export async function buildTestCommandWithFallback(workspace: WorkspaceInfo): Pr
|
|
|
94
94
|
case "unknown": {
|
|
95
95
|
if (packageManager === "bun") return ["bun", "test"];
|
|
96
96
|
if (packageManager === "npm") return ["npm", "test"];
|
|
97
|
-
if (packageManager === "yarn")
|
|
98
|
-
|
|
97
|
+
if (packageManager === "yarn") {
|
|
98
|
+
const cmd = await resolveAvailableCommand("yarn");
|
|
99
|
+
return cmd === "npx" ? ["npx", "yarn", "test"] : ["yarn", "test"];
|
|
100
|
+
}
|
|
101
|
+
if (packageManager === "pnpm") {
|
|
102
|
+
const cmd = await resolveAvailableCommand("pnpm");
|
|
103
|
+
return cmd === "npx" ? ["npx", "pnpm", "test"] : ["pnpm", "test"];
|
|
104
|
+
}
|
|
99
105
|
if (packageManager === "uv") return ["uv", "run", "pytest", "-v"];
|
|
100
106
|
if (packageManager === "poetry") return ["poetry", "run", "pytest", "-v"];
|
|
101
107
|
return ["npm", "test"];
|
|
@@ -111,79 +117,6 @@ export async function buildTestCommandWithFallback(workspace: WorkspaceInfo): Pr
|
|
|
111
117
|
}
|
|
112
118
|
}
|
|
113
119
|
|
|
114
|
-
/**
|
|
115
|
-
* Build test command based on detected test framework
|
|
116
|
-
*/
|
|
117
|
-
export function buildTestCommand(workspace: WorkspaceInfo): string[] {
|
|
118
|
-
const { testFramework, packageManager } = workspace;
|
|
119
|
-
|
|
120
|
-
switch (testFramework) {
|
|
121
|
-
case "bun-test":
|
|
122
|
-
return ["bun", "test"];
|
|
123
|
-
|
|
124
|
-
case "vitest":
|
|
125
|
-
if (packageManager === "npm") return ["npm", "run", "test"];
|
|
126
|
-
if (packageManager === "yarn") return ["yarn", "test"];
|
|
127
|
-
if (packageManager === "pnpm") return ["pnpm", "test"];
|
|
128
|
-
return ["npx", "vitest", "run"];
|
|
129
|
-
|
|
130
|
-
case "jest":
|
|
131
|
-
if (packageManager === "npm") return ["npm", "test"];
|
|
132
|
-
if (packageManager === "yarn") return ["yarn", "test"];
|
|
133
|
-
if (packageManager === "pnpm") return ["pnpm", "test"];
|
|
134
|
-
return ["npx", "jest"];
|
|
135
|
-
|
|
136
|
-
case "mocha":
|
|
137
|
-
if (packageManager === "npm") return ["npm", "test"];
|
|
138
|
-
if (packageManager === "yarn") return ["yarn", "test"];
|
|
139
|
-
return ["npx", "mocha"];
|
|
140
|
-
|
|
141
|
-
case "pytest": {
|
|
142
|
-
const { packageManager } = workspace;
|
|
143
|
-
if (packageManager === "uv") return ["uv", "run", "pytest", "-v"];
|
|
144
|
-
if (packageManager === "poetry") return ["poetry", "run", "pytest", "-v"];
|
|
145
|
-
return ["pytest", "-v"];
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
case "go-test":
|
|
149
|
-
return ["go", "test", "./..."];
|
|
150
|
-
|
|
151
|
-
case "cargo":
|
|
152
|
-
return ["cargo", "test"];
|
|
153
|
-
|
|
154
|
-
case "maven":
|
|
155
|
-
return ["mvn", "test"];
|
|
156
|
-
|
|
157
|
-
case "gradle":
|
|
158
|
-
return ["./gradlew", "test"];
|
|
159
|
-
|
|
160
|
-
case "playwright":
|
|
161
|
-
return ["npx", "playwright", "test"];
|
|
162
|
-
|
|
163
|
-
case "terraform":
|
|
164
|
-
return ["terraform", "validate"];
|
|
165
|
-
|
|
166
|
-
case "unknown": {
|
|
167
|
-
// Fallback: use package manager-based test command
|
|
168
|
-
const { packageManager } = workspace;
|
|
169
|
-
if (packageManager === "bun") return ["bun", "test"];
|
|
170
|
-
if (packageManager === "npm") return ["npm", "test"];
|
|
171
|
-
if (packageManager === "yarn") return ["yarn", "test"];
|
|
172
|
-
if (packageManager === "pnpm") return ["pnpm", "test"];
|
|
173
|
-
if (packageManager === "uv") return ["uv", "run", "pytest", "-v"];
|
|
174
|
-
if (packageManager === "poetry") return ["poetry", "run", "pytest", "-v"];
|
|
175
|
-
return ["npm", "test"];
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
default: {
|
|
179
|
-
const exhaustive: never = testFramework;
|
|
180
|
-
throw new TestRunnerError(
|
|
181
|
-
`Unsupported test framework: ${exhaustive}`,
|
|
182
|
-
{ testFramework }
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
120
|
|
|
188
121
|
/**
|
|
189
122
|
* Run tests in workspace and return result
|
|
@@ -196,7 +129,7 @@ export async function runTests(
|
|
|
196
129
|
spawner?: ProcessSpawner,
|
|
197
130
|
timeout = 300000
|
|
198
131
|
): Promise<TestResult> {
|
|
199
|
-
const command =
|
|
132
|
+
const command = await buildTestCommandWithFallback(workspace);
|
|
200
133
|
const [cmd, ...args] = command;
|
|
201
134
|
|
|
202
135
|
if (!cmd) {
|