tina4-nodejs 3.10.34 → 3.10.40
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/packages/cli/src/bin.ts +13 -26
- package/packages/cli/src/commands/seed.ts +72 -0
- package/packages/cli/src/commands/serve.ts +2 -1
- package/packages/core/src/ai.ts +241 -247
- package/packages/core/src/devAdmin.ts +289 -6
- package/packages/core/src/index.ts +3 -3
- package/packages/core/src/metrics.ts +800 -0
- package/packages/core/src/response.ts +98 -40
- package/packages/core/src/router.ts +5 -0
- package/packages/core/src/server.ts +3 -8
- package/packages/core/src/types.ts +2 -2
- package/packages/orm/src/baseModel.ts +25 -0
- package/packages/orm/src/database.ts +38 -0
|
@@ -17,6 +17,7 @@ import type { Router } from "./router.js";
|
|
|
17
17
|
import type { RouteHandler } from "./types.js";
|
|
18
18
|
import { DevMailbox } from "./devMailbox.js";
|
|
19
19
|
import { isTruthy } from "./dotenv.js";
|
|
20
|
+
import { quickMetrics, fullAnalysis, fileDetail } from "./metrics.js";
|
|
20
21
|
|
|
21
22
|
const cpuCount = osCpus().length;
|
|
22
23
|
|
|
@@ -343,6 +344,7 @@ export class DevAdmin {
|
|
|
343
344
|
const routes: Array<{ method: string; pattern: string; handler: RouteHandler }> = [
|
|
344
345
|
// Dashboard
|
|
345
346
|
{ method: "GET", pattern: "/__dev", handler: handleDashboard },
|
|
347
|
+
{ method: "GET", pattern: "/__dev/", handler: handleDashboard },
|
|
346
348
|
// Status & system
|
|
347
349
|
{ method: "GET", pattern: "/__dev/api/status", handler: handleStatus(router) },
|
|
348
350
|
{ method: "GET", pattern: "/__dev/api/system", handler: handleSystem },
|
|
@@ -388,6 +390,12 @@ export class DevAdmin {
|
|
|
388
390
|
// Gallery
|
|
389
391
|
{ method: "GET", pattern: "/__dev/api/gallery", handler: handleGalleryList },
|
|
390
392
|
{ method: "POST", pattern: "/__dev/api/gallery/deploy", handler: handleGalleryDeploy(router) },
|
|
393
|
+
// Metrics
|
|
394
|
+
{ method: "GET", pattern: "/__dev/api/metrics", handler: (_req: any, res: any) => { res.json(quickMetrics()); } },
|
|
395
|
+
{ method: "GET", pattern: "/__dev/api/metrics/full", handler: (_req: any, res: any) => { res.json(fullAnalysis()); } },
|
|
396
|
+
{ method: "GET", pattern: "/__dev/api/metrics/file", handler: (req: any, res: any) => { const url = new URL(req.url ?? "/", "http://localhost"); const p = (url.searchParams.get("path") || "").toString(); res.json(fileDetail(p)); } },
|
|
397
|
+
// Version check (proxy to avoid CORS)
|
|
398
|
+
{ method: "GET", pattern: "/__dev/api/version-check", handler: handleVersionCheck },
|
|
391
399
|
// JS asset
|
|
392
400
|
{ method: "GET", pattern: "/__dev/js/tina4-dev-admin.min.js", handler: handleDevAdminJs },
|
|
393
401
|
];
|
|
@@ -1040,6 +1048,30 @@ function handleGalleryDeploy(router: Router): RouteHandler {
|
|
|
1040
1048
|
};
|
|
1041
1049
|
}
|
|
1042
1050
|
|
|
1051
|
+
// ---------------------------------------------------------------------------
|
|
1052
|
+
// Version check — proxy to npm registry to avoid browser CORS errors
|
|
1053
|
+
// ---------------------------------------------------------------------------
|
|
1054
|
+
|
|
1055
|
+
const handleVersionCheck: RouteHandler = async (_req, res) => {
|
|
1056
|
+
const current = TINA4_VERSION;
|
|
1057
|
+
let latest = current;
|
|
1058
|
+
try {
|
|
1059
|
+
const controller = new AbortController();
|
|
1060
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
1061
|
+
const resp = await fetch("https://registry.npmjs.org/tina4-nodejs/latest", {
|
|
1062
|
+
signal: controller.signal,
|
|
1063
|
+
});
|
|
1064
|
+
clearTimeout(timeout);
|
|
1065
|
+
if (resp.ok) {
|
|
1066
|
+
const data = (await resp.json()) as Record<string, unknown>;
|
|
1067
|
+
if (typeof data.version === "string") latest = data.version;
|
|
1068
|
+
}
|
|
1069
|
+
} catch {
|
|
1070
|
+
// Offline or timeout — return current as latest
|
|
1071
|
+
}
|
|
1072
|
+
res.json({ current, latest });
|
|
1073
|
+
};
|
|
1074
|
+
|
|
1043
1075
|
// ---------------------------------------------------------------------------
|
|
1044
1076
|
// Dev Admin JS handler — serves the shared JS file
|
|
1045
1077
|
// ---------------------------------------------------------------------------
|
|
@@ -1467,6 +1499,7 @@ body { font-family: var(--font); background: var(--bg); color: var(--text); font
|
|
|
1467
1499
|
.dev-header {
|
|
1468
1500
|
background: var(--surface); border-bottom: 1px solid var(--border);
|
|
1469
1501
|
padding: 0.75rem 1.5rem; display: flex; align-items: center; gap: 1rem;
|
|
1502
|
+
position: sticky; top: 0; z-index: 100;
|
|
1470
1503
|
}
|
|
1471
1504
|
.dev-header h1 { font-size: 1rem; font-weight: 600; }
|
|
1472
1505
|
.dev-header .badge {
|
|
@@ -1476,6 +1509,7 @@ body { font-family: var(--font); background: var(--bg); color: var(--text); font
|
|
|
1476
1509
|
.dev-tabs {
|
|
1477
1510
|
display: flex; gap: 0; background: var(--surface);
|
|
1478
1511
|
border-bottom: 1px solid var(--border); overflow-x: auto;
|
|
1512
|
+
position: sticky; top: 2.75rem; z-index: 100;
|
|
1479
1513
|
}
|
|
1480
1514
|
.dev-tab {
|
|
1481
1515
|
padding: 0.6rem 1rem; cursor: pointer; font-size: 0.8rem;
|
|
@@ -1489,10 +1523,10 @@ body { font-family: var(--font); background: var(--bg); color: var(--text); font
|
|
|
1489
1523
|
background: var(--border); color: var(--muted); padding: 0.1rem 0.4rem;
|
|
1490
1524
|
border-radius: 0.75rem; font-size: 0.65rem; margin-left: 0.25rem;
|
|
1491
1525
|
}
|
|
1492
|
-
.dev-content { padding:
|
|
1526
|
+
.dev-content { padding: 0.25rem; }
|
|
1493
1527
|
.dev-panel {
|
|
1494
1528
|
background: var(--surface); border: 1px solid var(--border);
|
|
1495
|
-
border-radius: var(--radius); overflow:
|
|
1529
|
+
border-radius: var(--radius); overflow: visible;
|
|
1496
1530
|
}
|
|
1497
1531
|
.dev-panel-header {
|
|
1498
1532
|
padding: 0.75rem 1rem; border-bottom: 1px solid var(--border);
|
|
@@ -1614,6 +1648,7 @@ code, .mono { font-family: var(--mono); font-size: 0.82rem; }
|
|
|
1614
1648
|
<button class="dev-tab" onclick="showTab('system', event)">System</button>
|
|
1615
1649
|
<button class="dev-tab" onclick="showTab('tools', event)">Tools</button>
|
|
1616
1650
|
<button class="dev-tab" onclick="showTab('connections', event)">Connections</button>
|
|
1651
|
+
<button class="dev-tab" onclick="showTab('metrics', event)">Metrics</button>
|
|
1617
1652
|
<button class="dev-tab" onclick="showTab('chat', event)">Tina4</button>
|
|
1618
1653
|
</div>
|
|
1619
1654
|
|
|
@@ -1952,6 +1987,30 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
1952
1987
|
});
|
|
1953
1988
|
</script>
|
|
1954
1989
|
|
|
1990
|
+
<!-- Metrics Panel -->
|
|
1991
|
+
<div id="panel-metrics" class="dev-panel hidden">
|
|
1992
|
+
<div class="dev-panel-header">
|
|
1993
|
+
<h2>Code Metrics</h2>
|
|
1994
|
+
<div>
|
|
1995
|
+
<button class="btn btn-sm" onclick="loadAllMetrics()">Refresh</button>
|
|
1996
|
+
</div>
|
|
1997
|
+
</div>
|
|
1998
|
+
<div id="metrics-bubble" style="margin:1rem;"></div>
|
|
1999
|
+
<div id="metrics-drilldown" style="margin:0 1rem;display:none;"></div>
|
|
2000
|
+
<div id="metrics-quick" class="sys-grid"></div>
|
|
2001
|
+
<div id="metrics-largest" style="margin-top:1rem;"></div>
|
|
2002
|
+
<div id="metrics-tables" style="margin-top:1rem;padding:0 1rem 1rem;overflow-x:auto;">
|
|
2003
|
+
<h3 style="margin:1rem 0 0.5rem;color:var(--primary);">File Analysis</h3>
|
|
2004
|
+
<div id="metrics-heatmap"></div>
|
|
2005
|
+
<h3 style="margin:1rem 0 0.5rem;color:var(--primary);">Most Complex Functions</h3>
|
|
2006
|
+
<div id="metrics-complex"></div>
|
|
2007
|
+
<h3 style="margin:1rem 0 0.5rem;color:var(--primary);">Coupling Analysis</h3>
|
|
2008
|
+
<div id="metrics-coupling"></div>
|
|
2009
|
+
<h3 style="margin:1rem 0 0.5rem;color:var(--primary);">Violations</h3>
|
|
2010
|
+
<div id="metrics-violations"></div>
|
|
2011
|
+
</div>
|
|
2012
|
+
</div>
|
|
2013
|
+
|
|
1955
2014
|
<!-- Chat Panel (Tina4) -->
|
|
1956
2015
|
<div id="panel-chat" class="dev-panel hidden">
|
|
1957
2016
|
<div class="dev-panel-header">
|
|
@@ -1981,6 +2040,225 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
1981
2040
|
|
|
1982
2041
|
<script src="/__dev/js/tina4-dev-admin.min.js"></script>
|
|
1983
2042
|
<script>
|
|
2043
|
+
// ── Metrics Panel JS ──
|
|
2044
|
+
var _metricsFullData=null;
|
|
2045
|
+
function miColor(mi){
|
|
2046
|
+
if(mi>=60) return 'rgb('+(Math.round(34+(1-((mi-60)/40))*186))+','+(Math.round(197-(1-((mi-60)/40))*50))+',0)';
|
|
2047
|
+
if(mi>=30) return 'rgb('+(Math.round(220+((60-mi)/30)*19))+','+(Math.round(180-((60-mi)/30)*112))+',0)';
|
|
2048
|
+
return 'rgb(239,'+(Math.round(68-mi*2))+',0)';
|
|
2049
|
+
}
|
|
2050
|
+
function renderBubbleChart(files){
|
|
2051
|
+
var container=document.getElementById('metrics-bubble');
|
|
2052
|
+
if(!files||!files.length){container.innerHTML='<p style="color:var(--muted);padding:1rem">No files to analyze</p>';return;}
|
|
2053
|
+
var W=container.offsetWidth||900,H=Math.max(450,Math.min(650,W*0.45));
|
|
2054
|
+
var maxLoc=Math.max.apply(null,files.map(function(f){return f.loc}))||1;
|
|
2055
|
+
var minR=14,maxR=Math.min(70,W/10);
|
|
2056
|
+
var sorted=files.slice().sort(function(a,b){return a.loc-b.loc});
|
|
2057
|
+
var cx=W/2,cy=H/2;
|
|
2058
|
+
var bubbles=[];
|
|
2059
|
+
var angle=0,spiralR=0;
|
|
2060
|
+
for(var i=0;i<sorted.length;i++){
|
|
2061
|
+
var f=sorted[i];
|
|
2062
|
+
var r=minR+Math.sqrt(f.loc/maxLoc)*(maxR-minR);
|
|
2063
|
+
var color=miColor(f.maintainability||0);
|
|
2064
|
+
var placed=false;
|
|
2065
|
+
for(var attempt=0;attempt<800;attempt++){
|
|
2066
|
+
var px=cx+spiralR*Math.cos(angle);
|
|
2067
|
+
var py=cy+spiralR*Math.sin(angle);
|
|
2068
|
+
var collides=false;
|
|
2069
|
+
for(var j=0;j<bubbles.length;j++){
|
|
2070
|
+
var dx=px-bubbles[j].x,dy=py-bubbles[j].y;
|
|
2071
|
+
if(Math.sqrt(dx*dx+dy*dy)<r+bubbles[j].r+2){collides=true;break;}
|
|
2072
|
+
}
|
|
2073
|
+
if(!collides&&px>r+2&&px<W-r-2&&py>r+25&&py<H-r-2){
|
|
2074
|
+
bubbles.push({x:px,y:py,r:r,color:color,f:f,angle:Math.random()*Math.PI*2,speed:0.3+Math.random()*0.5,drift:2+Math.random()*3});
|
|
2075
|
+
placed=true;break;
|
|
2076
|
+
}
|
|
2077
|
+
angle+=0.2;spiralR+=0.04;
|
|
2078
|
+
}
|
|
2079
|
+
if(!placed){bubbles.push({x:cx+(Math.random()-0.5)*W*0.3,y:cy+(Math.random()-0.5)*H*0.3,r:r,color:color,f:f,angle:Math.random()*Math.PI*2,speed:0.3+Math.random()*0.5,drift:2+Math.random()*3});}
|
|
2080
|
+
}
|
|
2081
|
+
var canvas=document.createElement('canvas');
|
|
2082
|
+
canvas.width=W;canvas.height=H;
|
|
2083
|
+
canvas.style.cssText='display:block;border:1px solid var(--border);border-radius:8px;cursor:pointer;background:#0f172a';
|
|
2084
|
+
container.innerHTML='<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:0.5rem"><h3 style="margin:0;color:var(--primary)">Code Landscape</h3><span style="font-size:0.7rem;color:var(--muted)">Click a bubble to drill down | Size=LOC | <span style="color:#22c55e">Green</span>=maintainable <span style="color:#eab308">Yellow</span>=moderate <span style="color:#ef4444">Red</span>=needs work</span></div>';
|
|
2085
|
+
container.appendChild(canvas);
|
|
2086
|
+
var ctx=canvas.getContext('2d');
|
|
2087
|
+
var hoveredIdx=-1;
|
|
2088
|
+
var t=0;
|
|
2089
|
+
function draw(){
|
|
2090
|
+
t+=0.016;
|
|
2091
|
+
ctx.clearRect(0,0,W,H);
|
|
2092
|
+
ctx.strokeStyle='rgba(255,255,255,0.03)';ctx.lineWidth=1;
|
|
2093
|
+
for(var gx=0;gx<W;gx+=50){ctx.beginPath();ctx.moveTo(gx,0);ctx.lineTo(gx,H);ctx.stroke();}
|
|
2094
|
+
for(var gy=0;gy<H;gy+=50){ctx.beginPath();ctx.moveTo(0,gy);ctx.lineTo(W,gy);ctx.stroke();}
|
|
2095
|
+
bubbles.forEach(function(b,idx){
|
|
2096
|
+
var ox=Math.sin(t*b.speed+b.angle)*b.drift;
|
|
2097
|
+
var oy=Math.cos(t*b.speed*0.7+b.angle+1)*b.drift*0.6;
|
|
2098
|
+
var bx=b.x+ox,by=b.y+oy;
|
|
2099
|
+
var isHovered=(idx===hoveredIdx);
|
|
2100
|
+
var drawR=isHovered?b.r+4:b.r;
|
|
2101
|
+
if(isHovered){
|
|
2102
|
+
ctx.beginPath();ctx.arc(bx,by,drawR+8,0,Math.PI*2);
|
|
2103
|
+
ctx.fillStyle='rgba(255,255,255,0.08)';ctx.fill();
|
|
2104
|
+
}
|
|
2105
|
+
ctx.beginPath();ctx.arc(bx,by,drawR,0,Math.PI*2);
|
|
2106
|
+
ctx.fillStyle=b.color;ctx.globalAlpha=isHovered?0.95:0.7;ctx.fill();
|
|
2107
|
+
ctx.globalAlpha=1;ctx.strokeStyle=b.color;ctx.lineWidth=isHovered?2.5:1.5;ctx.stroke();
|
|
2108
|
+
var name=b.f.path.split('/').pop().replace('.ts','').replace('.js','');
|
|
2109
|
+
if(drawR>16){
|
|
2110
|
+
var fs=Math.max(8,Math.min(13,drawR*0.38));
|
|
2111
|
+
ctx.fillStyle='#fff';ctx.font='600 '+fs+'px monospace';ctx.textAlign='center';
|
|
2112
|
+
ctx.fillText(name,bx,by-2);
|
|
2113
|
+
ctx.fillStyle='rgba(255,255,255,0.65)';ctx.font=(fs-1)+'px monospace';
|
|
2114
|
+
ctx.fillText(b.f.loc+' LOC',bx,by+fs);
|
|
2115
|
+
if(isHovered&&drawR>25){
|
|
2116
|
+
ctx.fillStyle='rgba(255,255,255,0.5)';ctx.font=(fs-2)+'px monospace';
|
|
2117
|
+
ctx.fillText('CC:'+b.f.complexity+' MI:'+b.f.maintainability,bx,by+fs*2);
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
b._drawX=bx;b._drawY=by;b._drawR=drawR;
|
|
2121
|
+
});
|
|
2122
|
+
var totalLoc=0,totalFiles=bubbles.length;
|
|
2123
|
+
bubbles.forEach(function(b){totalLoc+=b.f.loc});
|
|
2124
|
+
var avgMI=bubbles.reduce(function(s,b){return s+b.f.maintainability},0)/totalFiles;
|
|
2125
|
+
ctx.fillStyle='rgba(255,255,255,0.35)';ctx.font='11px monospace';ctx.textAlign='right';
|
|
2126
|
+
ctx.fillText(totalFiles+' files | '+totalLoc.toLocaleString()+' LOC | Avg MI: '+avgMI.toFixed(1),W-12,H-10);
|
|
2127
|
+
window._metricsAnimFrame=requestAnimationFrame(draw);
|
|
2128
|
+
}
|
|
2129
|
+
draw();
|
|
2130
|
+
canvas.addEventListener('mousemove',function(e){
|
|
2131
|
+
var rect=canvas.getBoundingClientRect();
|
|
2132
|
+
var mx=e.clientX-rect.left,my=e.clientY-rect.top;
|
|
2133
|
+
hoveredIdx=-1;
|
|
2134
|
+
for(var i=bubbles.length-1;i>=0;i--){
|
|
2135
|
+
var b=bubbles[i];
|
|
2136
|
+
var dx=mx-b._drawX,dy=my-b._drawY;
|
|
2137
|
+
if(Math.sqrt(dx*dx+dy*dy)<=b._drawR){hoveredIdx=i;break;}
|
|
2138
|
+
}
|
|
2139
|
+
canvas.style.cursor=hoveredIdx>=0?'pointer':'default';
|
|
2140
|
+
});
|
|
2141
|
+
canvas.addEventListener('mouseleave',function(){hoveredIdx=-1;});
|
|
2142
|
+
canvas.addEventListener('click',function(e){
|
|
2143
|
+
if(hoveredIdx<0)return;
|
|
2144
|
+
var f=bubbles[hoveredIdx].f;
|
|
2145
|
+
drillDownFile(f.path);
|
|
2146
|
+
});
|
|
2147
|
+
}
|
|
2148
|
+
function drillDownFile(path){
|
|
2149
|
+
var dd=document.getElementById('metrics-drilldown');
|
|
2150
|
+
dd.style.display='block';
|
|
2151
|
+
dd.innerHTML='<div class="dev-panel" style="margin-bottom:1rem"><div class="dev-panel-header"><h2>'+path+'</h2><button class="btn btn-sm" onclick="document.getElementById('metrics-drilldown').style.display='none'">Close</button></div><div class="p-md"><p style="color:var(--muted)">Loading file analysis...</p></div></div>';
|
|
2152
|
+
fetch('/__dev/api/metrics/file?path='+encodeURIComponent(path)).then(function(r){return r.json()}).then(function(d){
|
|
2153
|
+
if(d.error){dd.querySelector('.p-md').innerHTML='<p style="color:var(--danger)">'+d.error+'</p>';return;}
|
|
2154
|
+
var html='<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:0.5rem;margin-bottom:1rem">';
|
|
2155
|
+
html+='<div class="sys-card"><div class="label">LOC</div><div class="value">'+d.loc+'</div></div>';
|
|
2156
|
+
html+='<div class="sys-card"><div class="label">Total Lines</div><div class="value">'+d.total_lines+'</div></div>';
|
|
2157
|
+
html+='<div class="sys-card"><div class="label">Classes</div><div class="value">'+d.classes+'</div></div>';
|
|
2158
|
+
html+='<div class="sys-card"><div class="label">Functions</div><div class="value">'+(d.functions?d.functions.length:0)+'</div></div>';
|
|
2159
|
+
html+='<div class="sys-card"><div class="label">Imports</div><div class="value">'+(d.imports?d.imports.length:0)+'</div></div>';
|
|
2160
|
+
html+='</div>';
|
|
2161
|
+
if(d.functions&&d.functions.length){
|
|
2162
|
+
html+='<h3 style="margin:0.5rem 0;color:var(--primary);font-size:0.85rem">Cyclomatic Complexity by Function</h3>';
|
|
2163
|
+
var maxCC=Math.max.apply(null,d.functions.map(function(f){return f.complexity}))||1;
|
|
2164
|
+
html+='<div style="display:flex;flex-direction:column;gap:4px">';
|
|
2165
|
+
d.functions.forEach(function(f){
|
|
2166
|
+
var pct=Math.max(3,f.complexity/maxCC*100);
|
|
2167
|
+
var color=f.complexity>20?'#ef4444':f.complexity>10?'#eab308':f.complexity>5?'#3b82f6':'#22c55e';
|
|
2168
|
+
html+='<div style="display:flex;align-items:center;gap:8px;font-size:0.75rem;font-family:var(--mono)">';
|
|
2169
|
+
html+='<span style="width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text)" title="'+f.name+'">'+f.name+'</span>';
|
|
2170
|
+
html+='<div style="flex:1;height:16px;background:var(--bg);border-radius:3px;overflow:hidden;position:relative">';
|
|
2171
|
+
html+='<div style="width:'+pct+'%;height:100%;background:'+color+';border-radius:3px;transition:width 0.3s"></div>';
|
|
2172
|
+
html+='</div>';
|
|
2173
|
+
html+='<span style="width:70px;text-align:right;color:'+color+';font-weight:600">CC:'+f.complexity+'</span>';
|
|
2174
|
+
html+='<span style="width:60px;text-align:right;color:var(--muted)">'+f.loc+' LOC</span>';
|
|
2175
|
+
html+='<span style="width:30px;text-align:right;color:var(--muted)">L'+f.line+'</span>';
|
|
2176
|
+
html+='</div>';
|
|
2177
|
+
});
|
|
2178
|
+
html+='</div>';
|
|
2179
|
+
}
|
|
2180
|
+
if(d.imports&&d.imports.length){
|
|
2181
|
+
html+='<h3 style="margin:0.75rem 0 0.25rem;color:var(--primary);font-size:0.85rem">Dependencies</h3>';
|
|
2182
|
+
html+='<div style="display:flex;flex-wrap:wrap;gap:4px">';
|
|
2183
|
+
d.imports.forEach(function(imp){
|
|
2184
|
+
html+='<span style="padding:2px 8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;font-size:0.7rem;font-family:var(--mono)">'+imp+'</span>';
|
|
2185
|
+
});
|
|
2186
|
+
html+='</div>';
|
|
2187
|
+
}
|
|
2188
|
+
dd.querySelector('.p-md').innerHTML=html;
|
|
2189
|
+
}).catch(function(e){
|
|
2190
|
+
dd.querySelector('.p-md').innerHTML='<p style="color:var(--danger)">Error: '+e.message+'</p>';
|
|
2191
|
+
});
|
|
2192
|
+
dd.scrollIntoView({behavior:'smooth',block:'start'});
|
|
2193
|
+
}
|
|
2194
|
+
function loadAllMetrics(){
|
|
2195
|
+
if(window._metricsAnimFrame)cancelAnimationFrame(window._metricsAnimFrame);
|
|
2196
|
+
var el=document.getElementById('metrics-quick');
|
|
2197
|
+
el.innerHTML='<div class="sys-card"><div class="value">Loading...</div></div>';
|
|
2198
|
+
fetch('/__dev/api/metrics').then(function(r){return r.json()}).then(function(d){
|
|
2199
|
+
if(d.error){el.innerHTML='<div class="sys-card"><div class="value" style="color:var(--danger)">'+d.error+'</div></div>';return;}
|
|
2200
|
+
el.innerHTML=
|
|
2201
|
+
'<div class="sys-card"><div class="label">TS/JS Files</div><div class="value">'+d.file_count+'</div></div>'+
|
|
2202
|
+
'<div class="sys-card"><div class="label">Lines of Code</div><div class="value">'+d.total_loc.toLocaleString()+'</div></div>'+
|
|
2203
|
+
'<div class="sys-card"><div class="label">Comment Lines</div><div class="value">'+d.total_comment.toLocaleString()+'</div></div>'+
|
|
2204
|
+
'<div class="sys-card"><div class="label">Blank Lines</div><div class="value">'+d.total_blank.toLocaleString()+'</div></div>'+
|
|
2205
|
+
'<div class="sys-card"><div class="label">Classes</div><div class="value">'+d.classes+'</div></div>'+
|
|
2206
|
+
'<div class="sys-card"><div class="label">Functions</div><div class="value">'+d.functions+'</div></div>'+
|
|
2207
|
+
'<div class="sys-card"><div class="label">Routes</div><div class="value">'+d.route_count+'</div></div>'+
|
|
2208
|
+
'<div class="sys-card"><div class="label">ORM Models</div><div class="value">'+d.orm_count+'</div></div>'+
|
|
2209
|
+
'<div class="sys-card"><div class="label">Templates</div><div class="value">'+d.template_count+'</div></div>'+
|
|
2210
|
+
'<div class="sys-card"><div class="label">Migrations</div><div class="value">'+d.migration_count+'</div></div>';
|
|
2211
|
+
}).catch(function(e){el.innerHTML='<div class="sys-card"><div class="value" style="color:var(--danger)">Error: '+e.message+'</div></div>';});
|
|
2212
|
+
document.getElementById('metrics-bubble').innerHTML='<p style="color:var(--muted);padding:1rem">Analyzing codebase...</p>';
|
|
2213
|
+
fetch('/__dev/api/metrics/full').then(function(r){return r.json()}).then(function(d){
|
|
2214
|
+
_metricsFullData=d;
|
|
2215
|
+
if(d.error){document.getElementById('metrics-bubble').innerHTML='<p style="color:var(--danger);padding:1rem">'+d.error+'</p>';return;}
|
|
2216
|
+
renderBubbleChart(d.file_metrics);
|
|
2217
|
+
var hm=document.getElementById('metrics-heatmap');
|
|
2218
|
+
var rows=d.file_metrics.map(function(f){
|
|
2219
|
+
var color=miColor(f.maintainability);
|
|
2220
|
+
var barW=Math.max(2,Math.min(100,f.maintainability));
|
|
2221
|
+
return '<tr style="cursor:pointer" onclick="drillDownFile(''+f.path+'')"><td><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:'+color+';margin-right:6px"></span>'+f.path+'</td><td>'+f.loc+'</td><td>'+f.complexity+'</td><td>'+f.avg_complexity+'</td><td><div style="display:flex;align-items:center;gap:6px"><div style="width:'+barW+'px;height:6px;border-radius:3px;background:'+color+'"></div><span>'+f.maintainability+'</span></div></td><td>'+f.instability+'</td></tr>';
|
|
2222
|
+
}).join('');
|
|
2223
|
+
hm.innerHTML='<table style="width:100%"><thead><tr><th>File</th><th>LOC</th><th>CC</th><th>Avg CC</th><th>MI</th><th>Instab.</th></tr></thead><tbody>'+rows+'</tbody></table>';
|
|
2224
|
+
var cf=document.getElementById('metrics-complex');
|
|
2225
|
+
var frows=d.most_complex_functions.map(function(f){
|
|
2226
|
+
var color=f.complexity>20?'#ef4444':f.complexity>10?'#eab308':'#22c55e';
|
|
2227
|
+
return '<tr style="cursor:pointer" onclick="drillDownFile(''+f.file+'')"><td><span style="color:'+color+';font-weight:bold">'+f.complexity+'</span></td><td>'+f.name+'</td><td>'+f.file+':'+f.line+'</td><td>'+f.loc+'</td></tr>';
|
|
2228
|
+
}).join('');
|
|
2229
|
+
cf.innerHTML='<table style="width:100%"><thead><tr><th>CC</th><th>Function</th><th>File</th><th>LOC</th></tr></thead><tbody>'+frows+'</tbody></table>';
|
|
2230
|
+
var cp=document.getElementById('metrics-coupling');
|
|
2231
|
+
var crows=d.file_metrics.filter(function(f){return f.coupling_afferent>0||f.coupling_efferent>0}).map(function(f){
|
|
2232
|
+
return '<tr style="cursor:pointer" onclick="drillDownFile(''+f.path+'')"><td>'+f.path+'</td><td>'+f.coupling_afferent+'</td><td>'+f.coupling_efferent+'</td><td>'+f.instability+'</td></tr>';
|
|
2233
|
+
}).join('');
|
|
2234
|
+
cp.innerHTML=crows?'<table style="width:100%"><thead><tr><th>File</th><th>Ca (in)</th><th>Ce (out)</th><th>Instability</th></tr></thead><tbody>'+crows+'</tbody></table>':'<p style="color:var(--muted)">No coupling data</p>';
|
|
2235
|
+
var vl=document.getElementById('metrics-violations');
|
|
2236
|
+
if(d.violations&&d.violations.length){
|
|
2237
|
+
var vrows=d.violations.map(function(v){
|
|
2238
|
+
var icon=v.type==='error'?'⚠':'ⓘ';
|
|
2239
|
+
var color=v.type==='error'?'#ef4444':'#eab308';
|
|
2240
|
+
return '<tr style="cursor:pointer" onclick="drillDownFile(''+v.file+'')"><td style="color:'+color+'">'+icon+'</td><td>'+v.message+'</td><td>'+v.file+(v.line?':'+v.line:'')+'</td></tr>';
|
|
2241
|
+
}).join('');
|
|
2242
|
+
vl.innerHTML='<table style="width:100%"><thead><tr><th></th><th>Issue</th><th>Location</th></tr></thead><tbody>'+vrows+'</tbody></table>';
|
|
2243
|
+
}else{
|
|
2244
|
+
vl.innerHTML='<p style="color:#22c55e">✓ No violations found</p>';
|
|
2245
|
+
}
|
|
2246
|
+
}).catch(function(e){
|
|
2247
|
+
document.getElementById('metrics-bubble').innerHTML='<p style="color:var(--danger);padding:1rem">Error: '+e.message+'</p>';
|
|
2248
|
+
});
|
|
2249
|
+
}
|
|
2250
|
+
var _metricsLoaded=false;
|
|
2251
|
+
var _origShowTab=typeof showTab==='function'?showTab:null;
|
|
2252
|
+
if(_origShowTab){
|
|
2253
|
+
showTab=function(name){
|
|
2254
|
+
_origShowTab(name);
|
|
2255
|
+
if(name==='metrics'&&!_metricsLoaded){_metricsLoaded=true;loadAllMetrics();}
|
|
2256
|
+
};
|
|
2257
|
+
}
|
|
2258
|
+
var metricsTab=document.querySelector('[onclick*="metrics"]');
|
|
2259
|
+
if(metricsTab)metricsTab.addEventListener('click',function(){if(!_metricsLoaded){_metricsLoaded=true;loadAllMetrics();}});
|
|
2260
|
+
</script>
|
|
2261
|
+
<script>
|
|
1984
2262
|
// Self-diagnostic — detect if the external JS failed to load
|
|
1985
2263
|
(function() {
|
|
1986
2264
|
if (typeof showTab !== 'function') {
|
|
@@ -2026,7 +2304,7 @@ function renderToolbarHtml(ctx: {
|
|
|
2026
2304
|
<span style="color:#ffeb3b;">req:${ctx.requestId}</span>
|
|
2027
2305
|
<span style="color:#90caf9;">${ctx.routeCount} routes</span>
|
|
2028
2306
|
<span style="color:#888;">Node.js ${nodeVersion}</span>
|
|
2029
|
-
<a href="#" onclick="(function(e){e.preventDefault();var p=document.getElementById('tina4-dev-panel');if(p){p.style.display=p.style.display==='none'?'block':'none';return;}var c=document.createElement('div');c.id='tina4-dev-panel';c.style.cssText='position:fixed;
|
|
2307
|
+
<a href="#" onclick="(function(e){e.preventDefault();var p=document.getElementById('tina4-dev-panel');if(p){p.style.display=p.style.display==='none'?'block':'none';return;}var c=document.createElement('div');c.id='tina4-dev-panel';c.style.cssText='position:fixed;top:3rem;left:0;right:0;bottom:2rem;z-index:99998;transition:all 0.2s';var f=document.createElement('iframe');f.src='/__dev';f.style.cssText='width:100%;height:100%;border:1px solid #2e7d32;border-radius:0.5rem;box-shadow:0 8px 32px rgba(0,0,0,0.5);background:#0f172a';c.appendChild(f);document.body.appendChild(c);})(event)" style="color:#ef9a9a;margin-left:auto;text-decoration:none;cursor:pointer;">Dashboard ↗</a>
|
|
2030
2308
|
<span onclick="this.parentElement.style.display='none'" style="cursor:pointer;color:#888;margin-left:8px;">✕</span>
|
|
2031
2309
|
</div>
|
|
2032
2310
|
<script>
|
|
@@ -2037,11 +2315,11 @@ function tina4VersionModal(){
|
|
|
2037
2315
|
var el=document.getElementById('tina4-ver-latest');
|
|
2038
2316
|
el.innerHTML='Checking for updates...';
|
|
2039
2317
|
el.style.color='#888';
|
|
2040
|
-
fetch('
|
|
2318
|
+
fetch('/__dev/api/version-check')
|
|
2041
2319
|
.then(function(r){return r.json()})
|
|
2042
2320
|
.then(function(d){
|
|
2043
|
-
var latest=d.
|
|
2044
|
-
var current=
|
|
2321
|
+
var latest=d.latest;
|
|
2322
|
+
var current=d.current;
|
|
2045
2323
|
if(latest===current){
|
|
2046
2324
|
el.innerHTML='Latest: <strong style="color:#a6e3a1;">v'+latest+'</strong> — You are up to date!';
|
|
2047
2325
|
el.style.color='#a6e3a1';
|
|
@@ -2054,6 +2332,8 @@ function tina4VersionModal(){
|
|
|
2054
2332
|
if(l>c){isNewer=true;break;}
|
|
2055
2333
|
if(l<c)break;
|
|
2056
2334
|
}
|
|
2335
|
+
var isAhead=false;
|
|
2336
|
+
if(!isNewer){for(var i=0;i<Math.max(cParts.length,lParts.length);i++){var c2=cParts[i]||0,l2=lParts[i]||0;if(c2>l2){isAhead=true;break;}if(c2<l2)break;}}
|
|
2057
2337
|
if(isNewer){
|
|
2058
2338
|
var breaking=(lParts[0]!==cParts[0]||lParts[1]!==cParts[1]);
|
|
2059
2339
|
el.innerHTML='Latest: <strong style="color:#f9e2af;">v'+latest+'</strong>';
|
|
@@ -2062,6 +2342,9 @@ function tina4VersionModal(){
|
|
|
2062
2342
|
}else{
|
|
2063
2343
|
el.innerHTML+='<div style="color:#f9e2af;margin-top:6px;">Patch update available. Run: <code style="background:#313244;padding:2px 6px;border-radius:3px;">npm install tina4-nodejs@latest</code></div>';
|
|
2064
2344
|
}
|
|
2345
|
+
}else if(isAhead){
|
|
2346
|
+
el.innerHTML='You are running <strong style="color:#cba6f7;">v'+current+'</strong> (ahead of npm <strong>v'+latest+'</strong> — not yet published).';
|
|
2347
|
+
el.style.color='#cba6f7';
|
|
2065
2348
|
}else{
|
|
2066
2349
|
el.innerHTML='Latest: <strong style="color:#a6e3a1;">v'+latest+'</strong> — You are up to date!';
|
|
2067
2350
|
el.style.color='#a6e3a1';
|
|
@@ -20,7 +20,7 @@ export { discoverRoutes } from "./routeDiscovery.js";
|
|
|
20
20
|
export { MiddlewareChain, MiddlewareRunner, cors, requestLogger, CorsMiddleware, RateLimiterMiddleware, RequestLogger, SecurityHeadersMiddleware, CsrfMiddleware } from "./middleware.js";
|
|
21
21
|
export type { CorsConfig } from "./middleware.js";
|
|
22
22
|
export { createRequest, parseBody } from "./request.js";
|
|
23
|
-
export { createResponse, errorResponse } from "./response.js";
|
|
23
|
+
export { createResponse, errorResponse, setDefaultTemplatesDir } from "./response.js";
|
|
24
24
|
export { tryServeStatic } from "./static.js";
|
|
25
25
|
export { loadEnv, getEnv, requireEnv, hasEnv, allEnv, resetEnv, isTruthy } from "./dotenv.js";
|
|
26
26
|
export { Log } from "./logger.js";
|
|
@@ -76,8 +76,8 @@ export { WSDLService, WSDLOp } from "./wsdl.js";
|
|
|
76
76
|
export type { WSDLOperation } from "./wsdl.js";
|
|
77
77
|
export { HtmlElement, htmlElement, addHtmlHelpers } from "./htmlElement.js";
|
|
78
78
|
export { renderErrorOverlay, renderProductionError, isDebugMode } from "./errorOverlay.js";
|
|
79
|
-
export {
|
|
80
|
-
export type { AiTool
|
|
79
|
+
export { AI_TOOLS, isInstalled, showMenu, installSelected, installAll, generateContext } from "./ai.js";
|
|
80
|
+
export type { AiTool } from "./ai.js";
|
|
81
81
|
export type { ImapMessage, ImapFullMessage } from "./messenger.js";
|
|
82
82
|
export { RabbitMQBackend } from "./queueBackends/rabbitmqBackend.js";
|
|
83
83
|
export type { RabbitMQConfig } from "./queueBackends/rabbitmqBackend.js";
|