@visulima/vis 1.0.0-alpha.11 → 1.0.0-alpha.13

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 (116) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/LICENSE.md +559 -186
  3. package/README.md +18 -0
  4. package/dist/bin.js +1 -9
  5. package/dist/config/index.d.ts +477 -556
  6. package/dist/config/index.js +1 -2
  7. package/dist/generate/index.js +1 -3
  8. package/dist/packem_chunks/applyDefaults.js +2 -336
  9. package/dist/packem_chunks/bin.js +234 -9552
  10. package/dist/packem_chunks/doctor-probe.js +2 -112
  11. package/dist/packem_chunks/fix.js +11 -234
  12. package/dist/packem_chunks/handler.js +1 -99
  13. package/dist/packem_chunks/handler10.js +2 -53
  14. package/dist/packem_chunks/handler11.js +1 -32
  15. package/dist/packem_chunks/handler12.js +5 -100
  16. package/dist/packem_chunks/handler13.js +1 -25
  17. package/dist/packem_chunks/handler14.js +18 -916
  18. package/dist/packem_chunks/handler15.js +15 -201
  19. package/dist/packem_chunks/handler16.js +1 -124
  20. package/dist/packem_chunks/handler17.js +1 -13
  21. package/dist/packem_chunks/handler18.js +1 -106
  22. package/dist/packem_chunks/handler19.js +1 -19
  23. package/dist/packem_chunks/handler2.js +2 -75
  24. package/dist/packem_chunks/handler20.js +5 -29
  25. package/dist/packem_chunks/handler21.js +1 -222
  26. package/dist/packem_chunks/handler22.js +1 -237
  27. package/dist/packem_chunks/handler23.js +5 -101
  28. package/dist/packem_chunks/handler24.js +1 -110
  29. package/dist/packem_chunks/handler25.js +3 -402
  30. package/dist/packem_chunks/handler26.js +1 -13
  31. package/dist/packem_chunks/handler27.js +1 -63
  32. package/dist/packem_chunks/handler28.js +7 -34
  33. package/dist/packem_chunks/handler29.js +21 -456
  34. package/dist/packem_chunks/handler3.js +4 -95
  35. package/dist/packem_chunks/handler30.js +3 -170
  36. package/dist/packem_chunks/handler31.js +1 -530
  37. package/dist/packem_chunks/handler32.js +2 -214
  38. package/dist/packem_chunks/handler33.js +25 -119
  39. package/dist/packem_chunks/handler34.js +2 -630
  40. package/dist/packem_chunks/handler35.js +3 -283
  41. package/dist/packem_chunks/handler36.js +22 -542
  42. package/dist/packem_chunks/handler37.js +410 -744
  43. package/dist/packem_chunks/handler38.js +22 -989
  44. package/dist/packem_chunks/handler39.js +22 -574
  45. package/dist/packem_chunks/handler4.js +2 -90
  46. package/dist/packem_chunks/handler40.js +22 -1685
  47. package/dist/packem_chunks/handler41.js +6 -1088
  48. package/dist/packem_chunks/handler42.js +5 -797
  49. package/dist/packem_chunks/handler43.js +10 -2658
  50. package/dist/packem_chunks/handler44.js +51 -3784
  51. package/dist/packem_chunks/handler45.js +25 -2574
  52. package/dist/packem_chunks/handler46.js +3 -3769
  53. package/dist/packem_chunks/handler47.js +21 -1485
  54. package/dist/packem_chunks/handler48.js +42 -0
  55. package/dist/packem_chunks/handler5.js +8 -174
  56. package/dist/packem_chunks/handler6.js +1 -95
  57. package/dist/packem_chunks/handler7.js +1 -115
  58. package/dist/packem_chunks/handler8.js +1 -12
  59. package/dist/packem_chunks/handler9.js +1 -29
  60. package/dist/packem_chunks/heal-accept.js +10 -522
  61. package/dist/packem_chunks/heal.js +14 -673
  62. package/dist/packem_chunks/index.js +7 -873
  63. package/dist/packem_chunks/loader.js +1 -23
  64. package/dist/packem_chunks/tar.js +3 -0
  65. package/dist/packem_shared/ai-analysis-hm8d2W7z.js +67 -0
  66. package/dist/packem_shared/ai-cache-DoiF80AR.js +1 -0
  67. package/dist/packem_shared/ai-fix-nn4zOE95.js +43 -0
  68. package/dist/packem_shared/cache-directory-CwHlJhgx.js +1 -0
  69. package/dist/packem_shared/dependency-scan-COr5n63B.js +2 -0
  70. package/dist/packem_shared/docker-D6OGr5_S.js +2 -0
  71. package/dist/packem_shared/failure-log-iUVLf6ts.js +2 -0
  72. package/dist/packem_shared/flakiness-D9wf0t56.js +1 -0
  73. package/dist/packem_shared/giget-CcEy_Elm.js +2 -0
  74. package/dist/packem_shared/index-DH-5hsrC.js +1 -0
  75. package/dist/packem_shared/otel-DxDUPJJH.js +6 -0
  76. package/dist/packem_shared/otelPlugin-CQq6poq8.js +1 -0
  77. package/dist/packem_shared/registry-CkubDdiY.js +2 -0
  78. package/dist/packem_shared/run-summary-utils-BfBvjzhY.js +1 -0
  79. package/dist/packem_shared/runtime-check-BXZ43CBW.js +1 -0
  80. package/dist/packem_shared/selectors-BylODRiM.js +3 -0
  81. package/dist/packem_shared/symbols-CQmER5MT.js +1 -0
  82. package/dist/packem_shared/toolchain-BgBOUHII.js +5 -0
  83. package/dist/packem_shared/typosquats-CcZl99B1.js +1 -0
  84. package/dist/packem_shared/use-measured-height-DjYgUOKk.js +1 -0
  85. package/dist/packem_shared/utils-DrNg0XTR.js +1 -0
  86. package/dist/packem_shared/verify-Baj5mFJ7.js +1 -0
  87. package/dist/packem_shared/vis-update-app-D1jl0UZZ.js +1 -0
  88. package/dist/packem_shared/xxh3-DrAUNq4n.js +1 -0
  89. package/index.js +556 -727
  90. package/package.json +19 -29
  91. package/schemas/project.schema.json +739 -297
  92. package/schemas/vis-config.schema.json +3365 -384
  93. package/templates/buildkite-ci/template.yml +20 -20
  94. package/dist/packem_shared/VisUpdateApp-D-Yz_wvg.js +0 -1316
  95. package/dist/packem_shared/_commonjsHelpers-BqLXS_qQ.js +0 -5
  96. package/dist/packem_shared/ai-analysis-CHeB1joD.js +0 -367
  97. package/dist/packem_shared/ai-cache-Be_jexe4.js +0 -142
  98. package/dist/packem_shared/ai-fix-B9iQVcD2.js +0 -379
  99. package/dist/packem_shared/cache-directory-2qvs4goY.js +0 -98
  100. package/dist/packem_shared/catalog-BJTtyi-O.js +0 -1371
  101. package/dist/packem_shared/dependency-scan-A0KSklpG.js +0 -188
  102. package/dist/packem_shared/docker-2iZzc280.js +0 -181
  103. package/dist/packem_shared/failure-log-Cz3Z4SKL.js +0 -100
  104. package/dist/packem_shared/flakiness-goTxXuCX.js +0 -180
  105. package/dist/packem_shared/otel-DCvqCTz_.js +0 -158
  106. package/dist/packem_shared/otelPlugin-DFaLDvJf.js +0 -3
  107. package/dist/packem_shared/registry-CbqXI0rc.js +0 -272
  108. package/dist/packem_shared/run-summary-utils-PVMl4aIh.js +0 -130
  109. package/dist/packem_shared/runtime-check-Cobi3p6l.js +0 -127
  110. package/dist/packem_shared/selectors-SM69TfqC.js +0 -194
  111. package/dist/packem_shared/symbols-Ta7g2nU-.js +0 -14
  112. package/dist/packem_shared/toolchain-BdZd9eBi.js +0 -975
  113. package/dist/packem_shared/typosquats-C-bCh3PX.js +0 -1210
  114. package/dist/packem_shared/use-measured-height-CNP0vT4M.js +0 -20
  115. package/dist/packem_shared/utils-CthVdBPS.js +0 -40
  116. package/dist/packem_shared/xxh3-Ck8mXNg1.js +0 -239
@@ -1,762 +1,428 @@
1
- import { createRequire as __cjs_createRequire } from "node:module";
1
+ var oe=Object.defineProperty;var E=(o,e)=>oe(o,"name",{value:e,configurable:!0});import{createRequire as ie}from"node:module";import{dim as k,bold as S,cyan as z}from"@visulima/colorize";import{projectGraphToDot as ce}from"@visulima/task-runner";import{Box as i,Text as t,ScrollView as ae,ScrollBar as pe,useApp as he,useWindowSize as ue,useInput as fe,Dialog as ge,render as me}from"@visulima/tui";import{an as W,k as xe,y as be,s as ye}from"./bin.js";import we,{useSyncExternalStore as ve,useState as O,useRef as q,useMemo as _,useCallback as U,useEffect as Ce}from"react";import{jsx as n,jsxs as r}from"react/jsx-runtime";const le=ie(import.meta.url),I=typeof globalThis<"u"&&typeof globalThis.process<"u"?globalThis.process:process,de=E(o=>{if(typeof I<"u"&&I.versions&&I.versions.node){const[e,l]=I.versions.node.split(".").map(Number);if(e>22||e===22&&l>=3||e===20&&l>=16)return I.getBuiltinModule(o)}return le(o)},"__cjs_getBuiltinModule"),{writeFileSync:se}=de("node:fs");var Ae=Object.defineProperty,$=E((o,e)=>Ae(o,"name",{value:e,configurable:!0}),"n");const ke=$(o=>{const e=new Map;for(const[l,d]of Object.entries(o.dependencies))for(const c of d){const s=e.get(c.target)??[];s.push(l),e.set(c.target,s)}return Object.values(o.nodes).map(l=>({deps:o.dependencies[l.name]??[],name:l.name,reverseDeps:e.get(l.name)??[],type:l.type})).sort((l,d)=>l.type!==d.type?l.type==="application"?-1:1:l.name.localeCompare(d.name))},"buildNodes"),Ee=$((o,e,l)=>{let d=o;if(e==="app"?d=d.filter(c=>c.type==="application"):e==="lib"&&(d=d.filter(c=>c.type!=="application")),l){const c=l.toLowerCase();d=d.filter(s=>s.name.toLowerCase().includes(c))}return d},"filterNodes");class Me{static{E(this,"GraphStore")}static{$(this,"GraphStore")}#e;#n=new Set;#r;constructor(e){this.#r=e;const l=ke(e);this.#e={allNodes:l,filterActive:!1,filterText:"",filterType:"all",focusedPanel:"list",selectedIndex:0}}getSnapshot=$(()=>this.#e,"getSnapshot");subscribe=$(e=>(this.#n.add(e),()=>{this.#n.delete(e)}),"subscribe");getFilteredNodes(){return Ee(this.#e.allNodes,this.#e.filterType,this.#e.filterText)}getStats(){const e=this.#e.allNodes.filter(c=>c.type==="application").length,l=this.#e.allNodes.length,d=Object.values(this.#r.dependencies).reduce((c,s)=>c+s.length,0);return{apps:e,deps:d,libs:l-e,total:l}}setSelectedIndex(e){const l=this.getFilteredNodes(),d=l.length===0?-1:Math.max(0,Math.min(e,l.length-1));d!==this.#e.selectedIndex&&this.#t({...this.#e,selectedIndex:d})}setFocusedPanel(e){e!==this.#e.focusedPanel&&this.#t({...this.#e,focusedPanel:e})}setFilterType(e){e!==this.#e.filterType&&this.#t({...this.#e,filterType:e,selectedIndex:0})}setFilter(e){this.#t({...this.#e,filterText:e,selectedIndex:0})}setFilterActive(e){e!==this.#e.filterActive&&this.#t({...this.#e,filterActive:e,filterText:e?this.#e.filterText:"",selectedIndex:e?this.#e.selectedIndex:0})}#t(e){this.#e=e;for(const l of this.#n)try{l()}catch{}}}var Te=Object.defineProperty,Se=E((o,e)=>Te(o,"name",{value:e,configurable:!0}),"p");const je=Se(({focused:o,node:e,scrollRef:l})=>{const d=o?"white":"gray";if(!e)return n(i,{alignItems:"center",borderColor:"gray",borderStyle:"single",flexDirection:"column",flexGrow:1,justifyContent:"center",children:n(t,{dimColor:!0,children:"No project selected"})});const c=e.type==="application",s=c?"yellow":"cyan",u=c?"Application":"Library";return n(i,{borderColor:d,borderStyle:"single",borderTopRightTitle:` ${u} `,borderTopTitle:` ${e.name} `,flexDirection:"column",flexGrow:1,children:r(ae,{flexGrow:1,flexShrink:1,paddingX:2,ref:l,scrollbar:!0,scrollbarColor:"gray",children:[r(i,{flexDirection:"column",marginTop:1,children:[r(i,{children:[n(t,{dimColor:!0,children:"── "}),n(t,{bold:!0,color:"white",children:"DEPENDS ON"}),r(t,{dimColor:!0,children:[" ","(",e.deps.length,")"]})]}),e.deps.length===0?n(i,{marginTop:1,paddingLeft:2,children:n(t,{dimColor:!0,children:"No dependencies"})}):n(i,{flexDirection:"column",marginTop:1,children:e.deps.map(a=>r(i,{gap:1,paddingLeft:2,children:[n(t,{color:"cyan",children:"→"}),n(t,{children:a.target}),a.type!=="static"&&r(t,{dimColor:!0,children:["(",a.type,")"]})]},a.target))})]}),r(i,{flexDirection:"column",marginTop:1,children:[r(i,{children:[n(t,{dimColor:!0,children:"── "}),n(t,{bold:!0,color:"white",children:"REQUIRED BY"}),r(t,{dimColor:!0,children:[" ","(",e.reverseDeps.length,")"]})]}),e.reverseDeps.length===0?n(i,{marginTop:1,paddingLeft:2,children:n(t,{dimColor:!0,children:"No reverse dependencies"})}):n(i,{flexDirection:"column",marginTop:1,children:e.reverseDeps.map(a=>r(i,{gap:1,paddingLeft:2,children:[n(t,{color:"magenta",children:"←"}),n(t,{children:a})]},a))})]}),r(i,{flexDirection:"column",marginTop:1,children:[r(i,{children:[n(t,{dimColor:!0,children:"── "}),n(t,{bold:!0,color:"white",children:"INFO"})]}),r(i,{flexDirection:"column",marginTop:1,paddingLeft:2,children:[r(i,{children:[n(i,{width:16,children:n(t,{dimColor:!0,children:"Type:"})}),n(t,{color:s,children:u})]}),r(i,{children:[n(i,{width:16,children:n(t,{dimColor:!0,children:"Dependencies:"})}),n(t,{children:String(e.deps.length)})]}),r(i,{children:[n(i,{width:16,children:n(t,{dimColor:!0,children:"Required by:"})}),n(t,{children:String(e.reverseDeps.length)})]}),r(i,{children:[n(i,{width:16,children:n(t,{dimColor:!0,children:"Connectivity:"})}),n(t,{children:String(e.deps.length+e.reverseDeps.length)})]})]})]})]})})},"ProjectDetailPanel");var De=Object.defineProperty,B=E((o,e)=>De(o,"name",{value:e,configurable:!0}),"g");const Ne=[{key:"all",label:"All",shortcut:"1"},{key:"app",label:"Apps",shortcut:"2"},{key:"lib",label:"Libs",shortcut:"3"}],Y=B(({isSelected:o,node:e})=>{const l=e.type==="application",d=l?"yellow":"cyan",c=l?"app":"lib";return r(i,{flexShrink:0,height:1,children:[r(t,{children:[o?"▶":" "," "]}),n(i,{flexGrow:1,children:n(t,{bold:o,inverse:o,wrap:"truncate",children:e.name})}),r(t,{color:d,children:[" ",c]}),r(t,{dimColor:!0,children:[" ","→",e.deps.length," ","←",e.reverseDeps.length]})]})},"ProjectRow"),X=B(({count:o,label:e})=>r(i,{flexShrink:0,height:1,marginTop:1,children:[r(t,{dimColor:!0,children:["▼"," "]}),n(t,{bold:!0,color:"white",children:e.toUpperCase()}),r(t,{dimColor:!0,children:[" ","(",o,")"]})]}),"TypeHeader"),Ie=B(({filterActive:o,filterText:e,filterType:l,focused:d,nodes:c,scrollOffset:s,selectedIndex:u,stats:a,viewportHeight:y})=>{const g=d?"white":"gray",v=c.filter(w=>w.type==="application"),h=c.filter(w=>w.type!=="application"),f=[];let x=0;if(v.length>0){f.push(n(X,{count:v.length,label:"Applications"},"hdr-apps"));for(const w of v){const A=x;f.push(n(Y,{isSelected:A===u,node:w},w.name)),x++}}if(h.length>0){f.push(n(X,{count:h.length,label:"Libraries"},"hdr-libs"));for(const w of h){const A=x;f.push(n(Y,{isSelected:A===u,node:w},w.name)),x++}}let C=0;v.length>0&&(C+=2+v.length),h.length>0&&(C+=2+h.length);const M=C>y&&y>0;return r(i,{borderColor:g,borderStyle:"single",flexDirection:"column",flexGrow:1,children:[r(i,{flexShrink:0,gap:1,paddingX:1,children:[n(t,{bold:!0,inverse:!0,children:" VIS "}),r(t,{wrap:"truncate",children:[a.total," ","packages"]}),r(t,{dimColor:!0,children:["(",a.apps," ","apps,"," ",a.libs," ","libs,"," ",a.deps," ","deps)"]})]}),n(i,{flexShrink:0,gap:1,paddingX:1,paddingY:1,children:Ne.map(w=>{const A=l===w.key;return r(i,{children:[n(t,{dimColor:!A,children:"["}),n(t,{bold:A,color:A?"cyan":"gray",children:w.shortcut}),n(t,{dimColor:!A,children:"]"}),r(t,{color:A?"white":"gray",children:[" ",w.label]})]},w.key)})}),o&&r(i,{flexShrink:0,paddingX:1,children:[n(t,{bold:!0,color:"white",children:"/ "}),n(t,{children:e}),n(t,{inverse:!0,children:" "})]}),r(i,{flexDirection:"row",flexGrow:1,overflow:"hidden",children:[n(i,{flexDirection:"column",flexGrow:1,overflow:"hidden",paddingLeft:1,children:n(i,{flexDirection:"column",marginTop:-s,children:f})}),M&&n(i,{flexShrink:0,marginLeft:1,marginRight:1,children:n(pe,{contentHeight:C,placement:"inset",scrollOffset:s,style:"block",viewportHeight:y})})]})]})},"ProjectListPanel");var $e=Object.defineProperty,Pe=E((o,e)=>$e(o,"name",{value:e,configurable:!0}),"M$1");const Le=100,Oe=40,Be=10,J={1:"all",2:"app",3:"lib"},Re=Pe(({autoExitSeconds:o=0,store:e})=>{const{exit:l}=he(),{columns:d,rows:c}=ue(),s=ve(e.subscribe,e.getSnapshot),[u,a]=O(!1),y=q(null),g=q(null),[v,h]=O(0),[f,x]=O(!1),C=_(()=>e.getFilteredNodes(),[s.allNodes,s.filterType,s.filterText]),M=_(()=>e.getStats(),[s.allNodes]),w=C[s.selectedIndex]??null,A=U(m=>{const p=C.filter(T=>T.type==="application"),b=C.filter(T=>T.type!=="application");let j=0,L=0;if(p.length>0){j+=2;for(let T=0;T<p.length;T++){if(L===m)return j;j+=1,L++}}if(b.length>0){j+=2;for(let T=0;T<b.length;T++){if(L===m)return j;j+=1,L++}}return j},[C]),P=Math.max(1,c-8-(s.filterActive?1:0)),N=U(m=>{const p=A(m);h(b=>p>b+P-2?Math.max(0,p-P+2):p<b+1?Math.max(0,p-1):b)},[A,P]);if(Ce(()=>{g.current?.scrollToTop()},[w?.name]),fe((m,p)=>{if(m==="c"&&p.ctrl){l();return}if(!f){if(u){p.escape||m==="?"?a(!1):m==="q"?(a(!1),x(!0)):p.downArrow||m==="j"?y.current?.scrollBy(1):(p.upArrow||m==="k")&&y.current?.scrollBy(-1);return}if(m==="?"){a(!0);return}if(m==="q"){x(!0);return}if(p.tab){e.setFocusedPanel(s.focusedPanel==="list"?"detail":"list");return}if(J[m]){e.setFilterType(J[m]);return}if(s.filterActive){if(p.escape){e.setFilterActive(!1);return}if(p.return){e.setFilterActive(!1);return}if(p.backspace){e.setFilter(s.filterText.slice(0,-1));return}if(m&&!p.ctrl&&!p.meta){e.setFilter(s.filterText+m);return}return}if(s.focusedPanel==="list"){if(C.length===0){m==="/"&&e.setFilterActive(!0);return}if(p.downArrow||m==="j"){const b=Math.min(s.selectedIndex+1,C.length-1);e.setSelectedIndex(b),N(b);return}if(p.upArrow||m==="k"){const b=Math.max(s.selectedIndex-1,0);e.setSelectedIndex(b),N(b);return}if(p.pageDown){const b=Math.min(s.selectedIndex+10,C.length-1);e.setSelectedIndex(b),N(b);return}if(p.pageUp){const b=Math.max(s.selectedIndex-10,0);e.setSelectedIndex(b),N(b);return}if(p.home){e.setSelectedIndex(0),h(0);return}if(p.end){const b=C.length-1;e.setSelectedIndex(b),N(b);return}if(m==="/"){e.setFilterActive(!0);return}if(p.rightArrow){e.setFocusedPanel("detail");return}return}if(s.focusedPanel==="detail"){if(p.escape||p.leftArrow){e.setFocusedPanel("list");return}if(p.downArrow||m==="j"){g.current?.scrollBy(1);return}if(p.upArrow||m==="k"){g.current?.scrollBy(-1);return}if(p.pageDown){g.current?.scrollBy(10);return}if(p.pageUp){g.current?.scrollBy(-10);return}if(p.home){g.current?.scrollToTop();return}p.end&&g.current?.scrollToBottom()}}},{isActive:!0}),d<Oe||c<Be)return n(i,{alignItems:"center",height:c,justifyContent:"center",width:d,children:r(t,{color:"yellow",children:["Terminal too small (",d,"x",c,")"]})});const ne=d>=Le,R=n(i,{borderBottom:!1,borderColor:"gray",borderLeft:!1,borderRight:!1,borderStyle:"single",flexShrink:0,children:r(i,{flexWrap:"wrap",gap:2,paddingX:1,children:[r(i,{gap:1,children:[n(t,{bold:!0,color:"white",children:"q"}),n(t,{dimColor:!0,children:"QUIT"})]},"q"),r(i,{gap:1,children:[n(t,{bold:!0,color:"white",children:"?"}),n(t,{dimColor:!0,children:"HELP"})]},"?"),r(i,{gap:1,children:[n(t,{bold:!0,color:"white",children:"↑↓"}),n(t,{dimColor:!0,children:"NAV"})]},"nav"),r(i,{gap:1,children:[n(t,{bold:!0,color:"white",children:"1-3 /"}),n(t,{dimColor:!0,children:"FILTER"})]},"f"),r(i,{gap:1,children:[n(t,{bold:!0,color:"white",children:"Tab"}),n(t,{dimColor:!0,children:"PANEL"})]},"t")]})}),F=r(ge,{footer:r(t,{dimColor:!0,children:[n(t,{bold:!0,color:"white",children:"↑↓"})," ","scroll"," ",n(t,{bold:!0,color:"white",children:"?"}),"/",n(t,{bold:!0,color:"white",children:"Esc"})," ","close"]}),scrollRef:y,title:"KEYBOARD SHORTCUTS",visible:u,width:52,children:[r(i,{flexDirection:"column",marginBottom:1,children:[r(i,{marginBottom:1,children:[n(t,{dimColor:!0,children:"── "}),n(t,{bold:!0,color:"white",children:"NAVIGATION"})]}),r(i,{children:[n(i,{width:24,children:r(t,{children:[r(t,{bold:!0,color:"white",children:[" ","↑","/k"]}),n(t,{dimColor:!0,children:" Move up"})]})}),r(t,{children:[r(t,{bold:!0,color:"white",children:[" ","↓","/j"]}),n(t,{dimColor:!0,children:" Move down"})]})]}),r(t,{children:[r(t,{bold:!0,color:"white",children:[" ","Tab"]}),n(t,{dimColor:!0,children:" Switch panel"})]}),r(t,{children:[r(t,{bold:!0,color:"white",children:[" ","→","/","←"]}),n(t,{dimColor:!0,children:" Focus detail/list"})]}),r(t,{children:[r(t,{bold:!0,color:"white",children:[" ","PgUp/PgDn"]}),n(t,{dimColor:!0,children:" Jump 10 items"})]}),r(t,{children:[r(t,{bold:!0,color:"white",children:[" ","Home/End"]}),n(t,{dimColor:!0,children:" Jump to start/end"})]})]}),r(i,{flexDirection:"column",marginBottom:1,children:[r(i,{marginBottom:1,children:[n(t,{dimColor:!0,children:"── "}),n(t,{bold:!0,color:"white",children:"FILTERS"})]}),r(i,{children:[n(i,{width:24,children:r(t,{children:[r(t,{bold:!0,color:"white",children:[" ","1"]}),n(t,{dimColor:!0,children:" All"})]})}),r(t,{children:[r(t,{bold:!0,color:"white",children:[" ","2"]}),n(t,{dimColor:!0,children:" Apps only"})]})]}),r(t,{children:[r(t,{bold:!0,color:"white",children:[" ","3"]}),n(t,{dimColor:!0,children:" Libraries only"})]}),r(t,{children:[r(t,{bold:!0,color:"white",children:[" ","/"]}),n(t,{dimColor:!0,children:" Text filter"})]})]}),r(i,{flexDirection:"column",children:[r(i,{marginBottom:1,children:[n(t,{dimColor:!0,children:"── "}),n(t,{bold:!0,color:"white",children:"ACTIONS"})]}),r(t,{children:[r(t,{bold:!0,color:"white",children:[" ","q"]}),n(t,{dimColor:!0,children:" Quit"})]}),r(t,{children:[r(t,{bold:!0,color:"white",children:[" ","?"]}),n(t,{dimColor:!0,children:" Toggle help"})]})]})]}),H=n(Ie,{filterActive:s.filterActive,filterText:s.filterText,filterType:s.filterType,focused:s.focusedPanel==="list",nodes:C,scrollOffset:v,selectedIndex:s.selectedIndex,stats:M,viewportHeight:P}),G=n(je,{focused:s.focusedPanel==="detail",node:w,scrollRef:g});if(ne){const m=Math.floor(d*.35);return r(i,{flexDirection:"column",height:c,width:d,children:[r(i,{flexDirection:"row",flexGrow:1,children:[n(i,{flexGrow:1,children:H}),n(i,{width:m,children:G})]}),R,n(W,{autoExitSeconds:o??3,onCancel:E(()=>{x(!1)},"onCancel"),visible:f}),F]})}const re=Math.floor(c*.55);return r(i,{flexDirection:"column",height:c,width:d,children:[n(i,{height:re,children:H}),n(i,{flexGrow:1,children:G}),R,n(W,{autoExitSeconds:o??3,onCancel:E(()=>{x(!1)},"onCancel"),visible:f}),F]})},"VisGraphApp");var V=Object.freeze,ee=Object.defineProperty,D=E((o,e)=>ee(o,"name",{value:e,configurable:!0}),"x"),Fe=E((o,e)=>V(ee(o,"raw",{value:V(e||o.slice())})),"C");const te=D((o,e,l,d,c,s,u,a)=>{const y=l?k("└── "):k("├── "),g=c.has(o),v=g?k(" (*)"):"",h=d.get(o),f=h?.type==="application"?S(o):o;if(s.push(`${e}${y}${f}${v}`),g)return;c.add(o);const x=h?.deps??[],C=l?`${e} `:`${e}${k("│")} `;if(a>=u&&x.length>0){s.push(`${C}${k(`... ${x.length} more`)}`);return}for(let M=0;M<x.length;M++){const w=x[M];w&&te(w.target,C,M===x.length-1,d,c,s,u,a+1)}},"printDepsTree"),Q=D((o,e,l,d,c,s)=>{const u=e.get(o),a=u?.type==="application"?S(o):o;d.push(`${s}${a}`),l.add(o);const y=u?.deps??[];if(y.length===0){d.push(`${s} ${k("(no dependencies)")}`);return}if(c<=0){d.push(`${s} ${k(`... ${y.length} dependencies`)}`);return}for(let g=0;g<y.length;g++){const v=y[g];v&&te(v.target,s,g===y.length-1,e,l,d,c,1)}},"printRootProject"),K=D((o,e)=>{const l=new Map;for(const[h,f]of Object.entries(o.nodes))l.set(h,{deps:(o.dependencies[h]??[]).map(x=>({target:x.target,type:x.type})),name:h,type:f.type});const d=[],c=[];for(const[h,f]of l)f.type==="application"?d.push(h):c.push(h);d.sort(),c.sort();const s=d.length+c.length,u=Object.values(o.dependencies).reduce((h,f)=>h+f.length,0),a=[S("Project Dependency Graph"),""];if(d.length>0){a.push(` ${S(z(`Applications (${d.length})`))}`,"");for(const h of d)Q(h,l,new Set,a,e," "),a.push("")}if(c.length>0){a.push(` ${S(z(`Libraries (${c.length})`))}`,"");for(const h of c)Q(h,l,new Set,a,e," "),a.push("")}const y=process.stdout.columns||80;a.push(k("─".repeat(Math.min(y,60)))),a.push(`${S(String(s))} packages ${k("·")} ${S(String(u))} dependencies ${k("·")} ${S(String(d.length))} apps${k(",")} ${S(String(c.length))} libraries`);const g=new Set;let v=!1;for(const h of[...d,...c]){const f=l.get(h)?.deps??[];for(const x of f)g.has(x.target)&&(v=!0),g.add(x.target);g.add(h)}return v&&a.push(k("(*) = already shown above")),a.join(`
2
+ `)},"projectGraphToAscii"),He=D(o=>{const e=Object.values(o.nodes).map(l=>({name:l.name,type:l.type}));return{edges:Object.values(o.dependencies).flat(),nodes:e}},"projectGraphToJson");var Z;const Ge=D(o=>{const e=Object.values(o.nodes).map(u=>({name:u.name,type:u.type})),l=[];for(const u of Object.values(o.dependencies))for(const a of u)l.push({source:a.source,target:a.target,type:a.type});const d=e.filter(u=>u.type==="application"),c=e.filter(u=>u.type!=="application"),s={apps:d.length,edges:l,libs:c.length,nodes:e};return String.raw(Z||(Z=Fe([`<!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>Project Dependency Graph</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body { font-family: system-ui, -apple-system, sans-serif; background: #0f172a; color: #e2e8f0; overflow: hidden; }
10
+ svg { width: 100vw; height: 100vh; }
11
+ .edge { fill: none; marker-end: url(#arrow); }
12
+ .node rect { rx: 8; ry: 8; cursor: pointer; transition: stroke-width 0.15s; }
13
+ .node text { font-size: 12px; font-weight: 600; pointer-events: none; }
14
+ .node:hover rect { stroke-width: 2.5; stroke: #fff; }
15
+ #info { position: fixed; top: 16px; right: 16px; background: #1e293b; padding: 14px 20px; border-radius: 10px; font-size: 13px; border: 1px solid #334155; box-shadow: 0 4px 12px rgba(0,0,0,0.3); }
16
+ #info b { font-variant-numeric: tabular-nums; }
17
+ .app-count { color: #fbbf24; }
18
+ .lib-count { color: #38bdf8; }
19
+ .dep-count { color: #a78bfa; }
20
+ #legend { position: fixed; bottom: 16px; left: 16px; background: #1e293b; padding: 12px 16px; border-radius: 10px; font-size: 12px; border: 1px solid #334155; display: flex; gap: 16px; align-items: center; }
21
+ .legend-dot { width: 12px; height: 12px; border-radius: 3px; display: inline-block; vertical-align: middle; margin-right: 6px; }
22
+ #tooltip { position: fixed; display: none; background: #1e293b; border: 1px solid #475569; border-radius: 8px; padding: 12px 16px; font-size: 12px; max-width: 320px; box-shadow: 0 8px 24px rgba(0,0,0,0.4); z-index: 10; pointer-events: none; }
23
+ #tooltip h3 { font-size: 14px; margin-bottom: 6px; }
24
+ #tooltip .type-badge { display: inline-block; padding: 1px 8px; border-radius: 4px; font-size: 10px; font-weight: 700; text-transform: uppercase; margin-left: 8px; }
25
+ #tooltip .dep-section { margin-top: 8px; color: #94a3b8; }
26
+ #tooltip ul { list-style: none; padding-left: 0; margin-top: 4px; }
27
+ #tooltip li { padding: 1px 0; color: #cbd5e1; }
28
+ #tooltip li::before { content: "92 "; color: #64748b; }
29
+ </style>
30
+ </head>
31
+ <body>
32
+ <div id="info">
33
+ <b class="app-count">`,`</b> apps &middot;
34
+ <b class="lib-count">`,`</b> libraries &middot;
35
+ <b class="dep-count">`,`</b> dependencies
36
+ </div>
37
+ <div id="legend">
38
+ <span><span class="legend-dot" style="background:#fbbf24"></span>Application</span>
39
+ <span><span class="legend-dot" style="background:#38bdf8"></span>Library</span>
40
+ <span style="color:#64748b">&mdash; solid = static &nbsp; - - - = implicit</span>
41
+ </div>
42
+ <div id="tooltip"></div>
43
+ <svg id="graph">
44
+ <defs>
45
+ <marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto">
46
+ <path d="M 0 0 L 10 5 L 0 10 z" fill="#64748b"/>
47
+ </marker>
48
+ </defs>
49
+ </svg>
50
+ <script>
51
+ const data = `,`;
52
+ const svg = document.getElementById('graph');
53
+ const tooltip = document.getElementById('tooltip');
54
+ const W = window.innerWidth, H = window.innerHeight;
2
55
 
3
- const __cjs_require = __cjs_createRequire(import.meta.url);
56
+ // Build adjacency
57
+ const depMap = {}, rdepMap = {};
58
+ data.nodes.forEach(n => { depMap[n.name] = []; rdepMap[n.name] = []; });
59
+ data.edges.forEach(e => { depMap[e.source]?.push(e.target); rdepMap[e.target]?.push(e.source); });
4
60
 
5
- const __cjs_getProcess = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" ? globalThis.process : process;
61
+ // Escape text for safe DOM insertion
62
+ function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.textContent; }
6
63
 
7
- const __cjs_getBuiltinModule = (module) => {
8
- // Check if we're in Node.js and version supports getBuiltinModule
9
- if (typeof __cjs_getProcess !== "undefined" && __cjs_getProcess.versions && __cjs_getProcess.versions.node) {
10
- const [major, minor] = __cjs_getProcess.versions.node.split(".").map(Number);
11
- // Node.js 20.16.0+ and 22.3.0+
12
- if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
13
- return __cjs_getProcess.getBuiltinModule(module);
14
- }
15
- }
16
- // Fallback to createRequire
17
- return __cjs_require(module);
18
- };
64
+ // Force-directed layout
65
+ const repulsion = 5000 + data.nodes.length * 150;
66
+ const nodes = data.nodes.map(n => ({
67
+ ...n, x: W/2 + (Math.random()-0.5)*Math.min(W*0.6, 600),
68
+ y: H/2 + (Math.random()-0.5)*Math.min(H*0.6, 400), vx: 0, vy: 0
69
+ }));
70
+ const nodeMap = new Map(nodes.map(n => [n.name, n]));
71
+ const edges = data.edges.map(e => ({
72
+ source: nodeMap.get(e.source), target: nodeMap.get(e.target), type: e.type
73
+ }));
19
74
 
20
- import { dim, bold, cyan } from '@visulima/colorize';
21
- import { join, dirname, relative, isAbsolute, sep, resolve } from '@visulima/path';
22
- const {
23
- fileURLToPath
24
- } = __cjs_getBuiltinModule("node:url");
25
- import { isAccessibleSync, walkSync, ensureDirSync, writeFileSync } from '@visulima/fs';
26
- const {
27
- createInterface
28
- } = __cjs_getBuiltinModule("node:readline");
29
- const {
30
- mkdtempSync,
31
- rmSync
32
- } = __cjs_getBuiltinModule("node:fs");
33
- const {
34
- tmpdir
35
- } = __cjs_getBuiltinModule("node:os");
36
- import { downloadTemplate } from 'giget';
37
- import { p as pail } from './bin.js';
38
- const {
39
- spawnSync
40
- } = __cjs_getBuiltinModule("node:child_process");
75
+ for (let iter = 0; iter < 400; iter++) {
76
+ for (let i = 0; i < nodes.length; i++) {
77
+ for (let j = i+1; j < nodes.length; j++) {
78
+ let dx = nodes[j].x - nodes[i].x, dy = nodes[j].y - nodes[i].y;
79
+ let d = Math.sqrt(dx*dx + dy*dy) || 1;
80
+ let f = repulsion / (d * d);
81
+ nodes[i].vx -= dx/d * f; nodes[i].vy -= dy/d * f;
82
+ nodes[j].vx += dx/d * f; nodes[j].vy += dy/d * f;
83
+ }
84
+ }
85
+ edges.forEach(e => {
86
+ if (!e.source || !e.target) {
87
+ return;
88
+ }
89
+ let dx = e.target.x - e.source.x, dy = e.target.y - e.source.y;
90
+ let d = Math.sqrt(dx*dx + dy*dy) || 1;
91
+ let f = (d - 180) * 0.008;
92
+ e.source.vx += dx/d * f; e.source.vy += dy/d * f;
93
+ e.target.vx -= dx/d * f; e.target.vy -= dy/d * f;
94
+ });
95
+ nodes.forEach(n => {
96
+ n.vx += (W/2 - n.x) * 0.001; n.vy += (H/2 - n.y) * 0.001;
97
+ n.x += n.vx * 0.3; n.y += n.vy * 0.3;
98
+ n.vx *= 0.75; n.vy *= 0.75;
99
+ n.x = Math.max(80, Math.min(W-80, n.x));
100
+ n.y = Math.max(40, Math.min(H-40, n.y));
101
+ });
102
+ }
41
103
 
42
- const NATIVE_EXTENSIONS = [".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"];
43
- const TEMPLATE_YML = "template.yml";
44
- const BLOCKED_BASE_SUFFIXES = [".d", ".test", ".spec", ".config", ".bench", ".stories"];
45
- const BLOCKED_FULL_EXTENSIONS = [".d.ts", ".d.mts", ".d.cts", ".js.map", ".mjs.map", ".cjs.map", ".ts.map"];
46
- const stripExtension = (filename) => {
47
- for (const ext of NATIVE_EXTENSIONS) {
48
- if (filename.endsWith(ext)) {
49
- return filename.slice(0, -ext.length);
50
- }
51
- }
52
- return filename;
53
- };
54
- const isNativeFile = (filename) => {
55
- if (BLOCKED_FULL_EXTENSIONS.some((ext) => filename.endsWith(ext))) {
56
- return false;
57
- }
58
- if (!NATIVE_EXTENSIONS.some((ext) => filename.endsWith(ext))) {
59
- return false;
60
- }
61
- const base = stripExtension(filename);
62
- return !BLOCKED_BASE_SUFFIXES.some((suffix) => base.endsWith(suffix));
63
- };
64
- const scanNativeDirectory = (directory, source) => {
65
- const results = [];
66
- if (!isAccessibleSync(directory)) {
67
- return results;
68
- }
69
- for (const entry of walkSync(directory, { includeDirs: false, includeSymlinks: false, maxDepth: 1 })) {
70
- if (!isNativeFile(entry.name)) {
71
- continue;
72
- }
73
- const name = stripExtension(entry.name);
74
- results.push({
75
- // Lazy loader — module is loaded only when the user picks the template.
76
- load: () => loadNativeFromPath(entry.path),
77
- name,
78
- path: entry.path,
79
- source
80
- });
81
- }
82
- return results;
83
- };
84
- const scanMoonDirectory = (directory, source) => {
85
- const results = [];
86
- if (!isAccessibleSync(directory)) {
87
- return results;
88
- }
89
- for (const entry of walkSync(directory, { includeFiles: false, includeSymlinks: false, maxDepth: 1 })) {
90
- if (entry.path === directory) {
91
- continue;
92
- }
93
- const yamlPath = join(entry.path, TEMPLATE_YML);
94
- if (!isAccessibleSync(yamlPath)) {
95
- continue;
96
- }
97
- results.push({
98
- load: () => loadMoonFromPath(entry.path, entry.name),
99
- name: entry.name,
100
- path: entry.path,
101
- source
102
- });
103
- }
104
- return results;
105
- };
106
- const resolveBuiltinTemplatesDirectory = () => {
107
- try {
108
- const here = fileURLToPath(import.meta.url);
109
- const candidate = join(dirname(here), "..", "..", "templates");
110
- return isAccessibleSync(candidate) ? candidate : void 0;
111
- } catch {
112
- return void 0;
113
- }
114
- };
115
- const discoverTemplates = (options) => {
116
- const { extraDirectories = [], onWarning, workspaceRoot } = options;
117
- const results = [];
118
- results.push(...scanNativeDirectory(join(workspaceRoot, ".vis", "templates"), "native"));
119
- results.push(...scanMoonDirectory(join(workspaceRoot, ".vis", "templates"), "moon"));
120
- results.push(...scanMoonDirectory(join(workspaceRoot, ".moon", "templates"), "moon"));
121
- for (const directory of extraDirectories) {
122
- results.push(...scanMoonDirectory(directory, "config"));
123
- results.push(...scanNativeDirectory(directory, "config"));
124
- }
125
- const builtinDirectory = resolveBuiltinTemplatesDirectory();
126
- if (builtinDirectory) {
127
- results.push(...scanMoonDirectory(builtinDirectory, "builtin"));
104
+ // Measure text widths
105
+ const measure = document.createElementNS('http://www.w3.org/2000/svg','text');
106
+ measure.setAttribute('font-size','12'); measure.setAttribute('font-weight','600');
107
+ measure.setAttribute('font-family','system-ui');
108
+ svg.appendChild(measure);
109
+ const widths = {};
110
+ nodes.forEach(n => { measure.textContent = n.name; widths[n.name] = measure.getComputedTextLength(); });
111
+ svg.removeChild(measure);
112
+
113
+ // Render edges
114
+ edges.forEach(e => {
115
+ if (!e.source || !e.target) {
116
+ return;
128
117
  }
129
- const seen = /* @__PURE__ */ new Map();
130
- for (const template of results) {
131
- const existing = seen.get(template.name);
132
- if (!existing) {
133
- seen.set(template.name, template);
134
- continue;
135
- }
136
- if (onWarning) {
137
- onWarning(
138
- `Template "${template.name}" exists in multiple sources — using ${existing.source} (${existing.path}), ignoring ${template.source} (${template.path}).`
139
- );
118
+ const sw = (widths[e.source.name]||80)/2 + 12;
119
+ const tw = (widths[e.target.name]||80)/2 + 12;
120
+ const dx = e.target.x - e.source.x, dy = e.target.y - e.source.y;
121
+ const d = Math.sqrt(dx*dx+dy*dy)||1;
122
+ const x1 = e.source.x + dx/d*sw, y1 = e.source.y + dy/d*14;
123
+ const x2 = e.target.x - dx/d*tw, y2 = e.target.y - dy/d*14;
124
+ const line = document.createElementNS('http://www.w3.org/2000/svg','line');
125
+ line.setAttribute('x1',x1); line.setAttribute('y1',y1);
126
+ line.setAttribute('x2',x2); line.setAttribute('y2',y2);
127
+ line.setAttribute('class','edge');
128
+ const edgeColors = { implicit: '#475569', devDependency: '#888888', peerDependency: '#CC8800' };
129
+ line.setAttribute('stroke', edgeColors[e.type] || '#64748b');
130
+ line.setAttribute('stroke-width', '1.5');
131
+ if (e.type === 'implicit' || e.type === 'peerDependency') {
132
+ line.setAttribute('stroke-dasharray', '6,4');
133
+ }
134
+ if (e.type === 'devDependency') {
135
+ line.setAttribute('stroke-dasharray', '3,3');
136
+ }
137
+ svg.appendChild(line);
138
+ });
139
+
140
+ // Render nodes
141
+ nodes.forEach(n => {
142
+ const w = (widths[n.name]||80) + 24;
143
+ const h = 32;
144
+ const isApp = n.type === 'application';
145
+ const fill = isApp ? '#fbbf24' : '#38bdf8';
146
+ const textFill = '#0f172a';
147
+ const stroke = isApp ? '#f59e0b' : '#0284c7';
148
+
149
+ const g = document.createElementNS('http://www.w3.org/2000/svg','g');
150
+ g.setAttribute('class','node');
151
+ g.setAttribute('transform','translate('+(n.x - w/2)+','+(n.y - h/2)+')');
152
+ const rect = document.createElementNS('http://www.w3.org/2000/svg','rect');
153
+ rect.setAttribute('width', w); rect.setAttribute('height', h);
154
+ rect.setAttribute('fill', fill); rect.setAttribute('stroke', stroke); rect.setAttribute('stroke-width','1.5');
155
+ g.appendChild(rect);
156
+ const text = document.createElementNS('http://www.w3.org/2000/svg','text');
157
+ text.setAttribute('x', w/2); text.setAttribute('y', h/2 + 4.5);
158
+ text.setAttribute('text-anchor','middle'); text.setAttribute('fill', textFill);
159
+ text.textContent = n.name;
160
+ g.appendChild(text);
161
+
162
+ g.addEventListener('mouseenter', (ev) => {
163
+ const deps = depMap[n.name] || [];
164
+ const rdeps = rdepMap[n.name] || [];
165
+
166
+ // Build tooltip using safe DOM methods
167
+ tooltip.textContent = '';
168
+
169
+ const heading = document.createElement('h3');
170
+ heading.textContent = n.name;
171
+ const badge = document.createElement('span');
172
+ badge.className = 'type-badge';
173
+ badge.style.background = fill;
174
+ badge.style.color = '#0f172a';
175
+ badge.textContent = n.type;
176
+ heading.appendChild(badge);
177
+ tooltip.appendChild(heading);
178
+
179
+ if (deps.length) {
180
+ const label = document.createElement('div');
181
+ label.className = 'dep-section';
182
+ label.textContent = 'Depends on:';
183
+ tooltip.appendChild(label);
184
+ const ul = document.createElement('ul');
185
+ deps.forEach(d => { const li = document.createElement('li'); li.textContent = d; ul.appendChild(li); });
186
+ tooltip.appendChild(ul);
187
+ }
188
+ if (rdeps.length) {
189
+ const label = document.createElement('div');
190
+ label.className = 'dep-section';
191
+ label.textContent = 'Required by:';
192
+ tooltip.appendChild(label);
193
+ const ul = document.createElement('ul');
194
+ rdeps.forEach(d => { const li = document.createElement('li'); li.textContent = d; ul.appendChild(li); });
195
+ tooltip.appendChild(ul);
196
+ }
197
+ if (!deps.length && !rdeps.length) {
198
+ const empty = document.createElement('div');
199
+ empty.style.marginTop = '6px';
200
+ empty.style.color = '#64748b';
201
+ empty.textContent = 'No dependencies';
202
+ tooltip.appendChild(empty);
140
203
  }
141
- }
142
- return [...seen.values()].sort((a, b) => a.name.localeCompare(b.name));
143
- };
144
- const loadNativeFromPath = async (path) => {
145
- const { loadNativeTemplate } = await import('./loader.js');
146
- return loadNativeTemplate(path);
147
- };
148
- const loadMoonFromPath = async (path, name) => {
149
- const { loadMoonTemplate } = await import('./index.js');
150
- return loadMoonTemplate(path, name);
151
- };
152
204
 
153
- const ask = (rl, question) => new Promise((resolve) => {
154
- rl.question(question, (answer) => {
155
- resolve(answer.trim());
205
+ tooltip.style.display = 'block';
206
+ const rect = tooltip.getBoundingClientRect();
207
+ tooltip.style.left = Math.min(ev.clientX + 12, W - rect.width - 12) + 'px';
208
+ tooltip.style.top = Math.min(ev.clientY + 12, H - rect.height - 12) + 'px';
156
209
  });
210
+ g.addEventListener('mouseleave', () => { tooltip.style.display = 'none'; });
211
+ svg.appendChild(g);
157
212
  });
158
- const confirm = async (rl, question, defaultYes) => {
159
- const hint = defaultYes ? "[Y/n]" : "[y/N]";
160
- const answer = await ask(rl, ` ${question} ${dim(hint)} `);
161
- if (answer === "") {
162
- return defaultYes;
163
- }
164
- return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
165
- };
166
- const selectOne = async (rl, question, choices, defaultValue) => {
167
- process.stderr.write(` ${question}
168
- `);
169
- for (const [index, choice] of choices.entries()) {
170
- const prefix = bold(cyan(` ${String(index + 1)}.`));
171
- const star = choice === defaultValue ? dim(" (default)") : "";
172
- process.stderr.write(`${prefix} ${choice}${star}
173
- `);
174
- }
175
- while (true) {
176
- const answer = await ask(rl, `
177
- ${dim(`Enter choice (1-${String(choices.length)}):`)} `);
178
- if (answer === "" && defaultValue !== void 0) {
179
- return defaultValue;
180
- }
181
- const number_ = Number.parseInt(answer, 10);
182
- if (Number.isInteger(number_) && number_ >= 1 && number_ <= choices.length) {
183
- return choices[number_ - 1];
184
- }
185
- const match = choices.find((c) => c === answer);
186
- if (match) {
187
- return match;
188
- }
189
- process.stderr.write(` ${dim("Invalid choice. Try again.")}
190
- `);
191
- }
192
- };
193
- const selectMany = async (rl, question, choices, defaultValues) => {
194
- process.stderr.write(` ${question} ${dim("(comma-separated numbers)")}
195
- `);
196
- for (const [index, choice] of choices.entries()) {
197
- const prefix = bold(cyan(` ${String(index + 1)}.`));
198
- const star = defaultValues.includes(choice) ? dim(" (default)") : "";
199
- process.stderr.write(`${prefix} ${choice}${star}
200
- `);
201
- }
202
- while (true) {
203
- const answer = await ask(rl, `
204
- ${dim(`Enter choices:`)} `);
205
- if (answer === "" && defaultValues.length > 0) {
206
- return defaultValues;
207
- }
208
- const indices = answer.split(",").map((part) => Number.parseInt(part.trim(), 10)).filter((n) => Number.isInteger(n) && n >= 1 && n <= choices.length);
209
- if (indices.length > 0) {
210
- return indices.map((index) => choices[index - 1]);
211
- }
212
- process.stderr.write(` ${dim("Invalid choice. Try again.")}
213
- `);
214
- }
215
- };
216
- const promptText = (label) => ` ${dim(`${label}:`)} `;
217
- const variableLabel = (name, variable) => variable.prompt ?? name;
218
- const sortVariables = (variables) => Object.entries(variables).sort(([nameA, a], [nameB, b]) => {
219
- const orderA = a.order ?? 0;
220
- const orderB = b.order ?? 0;
221
- if (orderA !== orderB) {
222
- return orderA - orderB;
223
- }
224
- return nameA.localeCompare(nameB);
225
- });
226
- const parseValue = (variable, raw) => {
227
- switch (variable.type) {
228
- case "array": {
229
- return raw.split(",").map((s) => s.trim()).filter(Boolean);
230
- }
231
- case "boolean": {
232
- return raw === "true" || raw === "1" || raw === "yes" || raw === "y";
233
- }
234
- case "enum": {
235
- if (variable.multiple) {
236
- return raw.split(",").map((s) => s.trim()).filter(Boolean);
237
- }
238
- return raw;
239
- }
240
- case "number": {
241
- const parsed = Number(raw);
242
- if (Number.isNaN(parsed)) {
243
- throw new TypeError(`Expected a number, got "${raw}"`);
244
- }
245
- return parsed;
246
- }
247
- default: {
248
- return raw;
249
- }
250
- }
251
- };
252
- const validateValue = (name, variable, value) => {
253
- if (variable.type === "enum") {
254
- const values = Array.isArray(value) ? value : [value];
255
- for (const candidate of values) {
256
- if (typeof candidate !== "string" || !variable.values.includes(candidate)) {
257
- throw new Error(`Variable "${name}" must be one of: ${variable.values.join(", ")} (got "${String(candidate)}")`);
258
- }
259
- }
260
- }
261
- };
262
- const collectOptions = async (collectOptionsArguments) => {
263
- const { defaults, interactive, overrides, variables } = collectOptionsArguments;
264
- const result = {};
265
- const rl = interactive ? createInterface({ input: process.stdin, output: process.stderr }) : null;
266
- try {
267
- for (const [name, variable] of sortVariables(variables)) {
268
- if (Object.hasOwn(overrides, name)) {
269
- const value2 = parseValue(variable, overrides[name] ?? "");
270
- validateValue(name, variable, value2);
271
- result[name] = value2;
272
- continue;
273
- }
274
- if (defaults || !interactive || variable.internal) {
275
- if (variable.default !== void 0) {
276
- validateValue(name, variable, variable.default);
277
- result[name] = variable.default;
278
- continue;
279
- }
280
- if (variable.required) {
281
- throw new Error(`Required variable "${name}" not provided. Pass --${name}=<value> or remove --defaults.`);
282
- }
283
- continue;
284
- }
285
- const label = variableLabel(name, variable);
286
- let value;
287
- if (variable.type === "boolean") {
288
- value = await confirm(rl, label, Boolean(variable.default ?? false));
289
- } else if (variable.type === "enum") {
290
- if (variable.multiple) {
291
- const defaultValues = Array.isArray(variable.default) ? variable.default : [];
292
- value = await selectMany(rl, label, variable.values, defaultValues);
293
- } else {
294
- const defaultValue = typeof variable.default === "string" ? variable.default : void 0;
295
- value = await selectOne(rl, label, variable.values, defaultValue);
296
- }
297
- } else {
298
- const defaultHint = variable.default === void 0 ? "" : ` (${String(variable.default)})`;
299
- const raw = await ask(rl, promptText(`${label}${defaultHint}`));
300
- if (raw === "" && variable.default !== void 0) {
301
- value = variable.default;
302
- } else if (raw === "") {
303
- if (variable.required) {
304
- throw new Error(`Variable "${name}" is required`);
305
- }
306
- continue;
307
- } else {
308
- value = parseValue(variable, raw);
309
- }
310
- }
311
- validateValue(name, variable, value);
312
- result[name] = value;
313
- }
314
- return result;
315
- } finally {
316
- rl?.close();
317
- }
318
- };
213
+ <\/script>
214
+ </body>
215
+ </html>`],[`<!DOCTYPE html>
216
+ <html lang="en">
217
+ <head>
218
+ <meta charset="UTF-8">
219
+ <title>Project Dependency Graph</title>
220
+ <style>
221
+ * { margin: 0; padding: 0; box-sizing: border-box; }
222
+ body { font-family: system-ui, -apple-system, sans-serif; background: #0f172a; color: #e2e8f0; overflow: hidden; }
223
+ svg { width: 100vw; height: 100vh; }
224
+ .edge { fill: none; marker-end: url(#arrow); }
225
+ .node rect { rx: 8; ry: 8; cursor: pointer; transition: stroke-width 0.15s; }
226
+ .node text { font-size: 12px; font-weight: 600; pointer-events: none; }
227
+ .node:hover rect { stroke-width: 2.5; stroke: #fff; }
228
+ #info { position: fixed; top: 16px; right: 16px; background: #1e293b; padding: 14px 20px; border-radius: 10px; font-size: 13px; border: 1px solid #334155; box-shadow: 0 4px 12px rgba(0,0,0,0.3); }
229
+ #info b { font-variant-numeric: tabular-nums; }
230
+ .app-count { color: #fbbf24; }
231
+ .lib-count { color: #38bdf8; }
232
+ .dep-count { color: #a78bfa; }
233
+ #legend { position: fixed; bottom: 16px; left: 16px; background: #1e293b; padding: 12px 16px; border-radius: 10px; font-size: 12px; border: 1px solid #334155; display: flex; gap: 16px; align-items: center; }
234
+ .legend-dot { width: 12px; height: 12px; border-radius: 3px; display: inline-block; vertical-align: middle; margin-right: 6px; }
235
+ #tooltip { position: fixed; display: none; background: #1e293b; border: 1px solid #475569; border-radius: 8px; padding: 12px 16px; font-size: 12px; max-width: 320px; box-shadow: 0 8px 24px rgba(0,0,0,0.4); z-index: 10; pointer-events: none; }
236
+ #tooltip h3 { font-size: 14px; margin-bottom: 6px; }
237
+ #tooltip .type-badge { display: inline-block; padding: 1px 8px; border-radius: 4px; font-size: 10px; font-weight: 700; text-transform: uppercase; margin-left: 8px; }
238
+ #tooltip .dep-section { margin-top: 8px; color: #94a3b8; }
239
+ #tooltip ul { list-style: none; padding-left: 0; margin-top: 4px; }
240
+ #tooltip li { padding: 1px 0; color: #cbd5e1; }
241
+ #tooltip li::before { content: "\\2192 "; color: #64748b; }
242
+ </style>
243
+ </head>
244
+ <body>
245
+ <div id="info">
246
+ <b class="app-count">`,`</b> apps &middot;
247
+ <b class="lib-count">`,`</b> libraries &middot;
248
+ <b class="dep-count">`,`</b> dependencies
249
+ </div>
250
+ <div id="legend">
251
+ <span><span class="legend-dot" style="background:#fbbf24"></span>Application</span>
252
+ <span><span class="legend-dot" style="background:#38bdf8"></span>Library</span>
253
+ <span style="color:#64748b">&mdash; solid = static &nbsp; - - - = implicit</span>
254
+ </div>
255
+ <div id="tooltip"></div>
256
+ <svg id="graph">
257
+ <defs>
258
+ <marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto">
259
+ <path d="M 0 0 L 10 5 L 0 10 z" fill="#64748b"/>
260
+ </marker>
261
+ </defs>
262
+ </svg>
263
+ <script>
264
+ const data = `,`;
265
+ const svg = document.getElementById('graph');
266
+ const tooltip = document.getElementById('tooltip');
267
+ const W = window.innerWidth, H = window.innerHeight;
319
268
 
320
- const REMOTE_PROTOCOLS = ["git://", "npm://", "https://", "github:", "gitlab:", "bitbucket:", "sourcehut:"];
321
- const isRemoteSource = (input) => REMOTE_PROTOCOLS.some((protocol) => input.startsWith(protocol));
322
- const fetchRemoteTemplate = async (source, options = {}) => {
323
- const ownDirectory = options.targetDirectory === void 0;
324
- const directory = options.targetDirectory ?? mkdtempSync(join(tmpdir(), "vis-generate-"));
325
- const cleanup = () => {
326
- if (!ownDirectory) {
327
- return;
328
- }
329
- try {
330
- rmSync(directory, { force: true, recursive: true });
331
- } catch {
332
- }
333
- };
334
- pail.info(`Downloading ${source}…`);
335
- try {
336
- const result = await downloadTemplate(source, {
337
- auth: options.auth || process.env.GIGET_AUTH || process.env.GITHUB_TOKEN || process.env.GH_TOKEN || void 0,
338
- dir: directory,
339
- force: true,
340
- preferOffline: options.preferOffline
341
- });
342
- return { cleanup, directory: result.dir };
343
- } catch (error) {
344
- cleanup();
345
- const message = error instanceof Error ? error.message : String(error);
346
- pail.warn(`Failed to download template: ${message}`);
347
- throw error;
348
- }
349
- };
269
+ // Build adjacency
270
+ const depMap = {}, rdepMap = {};
271
+ data.nodes.forEach(n => { depMap[n.name] = []; rdepMap[n.name] = []; });
272
+ data.edges.forEach(e => { depMap[e.source]?.push(e.target); rdepMap[e.target]?.push(e.source); });
350
273
 
351
- const flattenTree = (tree, prefix = "") => {
352
- const result = [];
353
- for (const [key, value] of Object.entries(tree)) {
354
- const path = prefix ? `${prefix}/${key}` : key;
355
- if (typeof value === "string" || Buffer.isBuffer(value)) {
356
- result.push({ content: value, path });
357
- } else if (value && typeof value === "object") {
358
- result.push(...flattenTree(value, path));
359
- }
360
- }
361
- return result;
362
- };
363
- const formatSize = (bytes) => {
364
- if (bytes < 1024) {
365
- return `${bytes} B`;
366
- }
367
- if (bytes < 1024 * 1024) {
368
- return `${(bytes / 1024).toFixed(1)} KB`;
369
- }
370
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
371
- };
372
- const safeJoinDestination = (destination, relPath) => {
373
- if (isAbsolute(relPath)) {
374
- throw new Error(`Refusing to write outside destination: template produced absolute path "${relPath}".`);
375
- }
376
- const target = join(destination, relPath);
377
- const rel = relative(destination, target);
378
- if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
379
- throw new Error(`Refusing to write outside destination: "${relPath}" resolves to "${target}" which escapes "${destination}".`);
380
- }
381
- return target;
382
- };
383
- const writeOne = (destinationPath, content) => {
384
- ensureDirSync(dirname(destinationPath));
385
- writeFileSync(destinationPath, content);
386
- };
387
- const runScript = (script, cwd, silent = false) => {
388
- const commands = typeof script === "string" ? [script] : script.commands;
389
- const isSilent = typeof script === "string" ? silent : script.silent ?? silent;
390
- for (const command of commands) {
391
- if (!isSilent) {
392
- pail.info(`$ ${command}`);
393
- }
394
- const result = spawnSync(command, {
395
- cwd,
396
- shell: true,
397
- stdio: isSilent ? "ignore" : "inherit"
398
- });
399
- if (result.status !== 0) {
400
- pail.warn(`Script failed (exit ${String(result.status)}): ${command}`);
401
- return false;
402
- }
403
- }
404
- return true;
405
- };
406
- const groupByPhase = (scripts) => {
407
- const phases = /* @__PURE__ */ new Map();
408
- for (const script of scripts) {
409
- const phase = typeof script === "string" ? 0 : script.phase ?? 0;
410
- const bucket = phases.get(phase);
411
- if (bucket) {
412
- bucket.push(script);
413
- } else {
414
- phases.set(phase, [script]);
415
- }
416
- }
417
- return [...phases.entries()].sort(([a], [b]) => a - b);
418
- };
419
- const runTemplate = async (template, options) => {
420
- const builtins = {
421
- dest_dir: options.destination,
422
- dest_rel_dir: relative(options.workspaceRoot, options.destination) || ".",
423
- working_dir: options.cwd,
424
- workspace_root: options.workspaceRoot
425
- };
426
- const context = {
427
- builtins,
428
- options: options.options
429
- };
430
- const creation = await template.produce(context);
431
- const files = creation.files ? flattenTree(creation.files) : [];
432
- const filesMeta = creation.filesMeta ?? {};
433
- const plans = [];
434
- for (const file of files) {
435
- const target = safeJoinDestination(options.destination, file.path);
436
- plans.push({ file, meta: filesMeta[file.path] ?? {}, target });
437
- }
438
- if (options.dryRun) {
439
- pail.info(`${bold(cyan("Plan"))} ${dim("(dry-run, no files written)")}`);
440
- for (const plan of plans) {
441
- const size = Buffer.isBuffer(plan.file.content) ? plan.file.content.length : Buffer.byteLength(plan.file.content, "utf8");
442
- process.stderr.write(` ${dim("write")} ${plan.file.path} ${dim(`(${formatSize(size)})`)}
443
- `);
444
- }
445
- } else {
446
- ensureDirSync(options.destination);
447
- let written = 0;
448
- let skipped = 0;
449
- for (const plan of plans) {
450
- const { file, meta, target } = plan;
451
- const exists = isAccessibleSync(target);
452
- const effectiveForce = options.force || meta.force === true;
453
- if (exists && !effectiveForce) {
454
- pail.warn(`Skipped existing file: ${file.path} (use --force or set frontmatter force: true to overwrite)`);
455
- skipped += 1;
456
- continue;
457
- }
458
- writeOne(target, file.content);
459
- written += 1;
460
- }
461
- pail.success(`Wrote ${String(written)} file${written === 1 ? "" : "s"}${skipped > 0 ? `, skipped ${String(skipped)}` : ""}`);
462
- }
463
- if (!options.dryRun && !options.skipScripts && creation.scripts && creation.scripts.length > 0) {
464
- const phases = groupByPhase(creation.scripts);
465
- pail.info(
466
- `Running ${String(creation.scripts.length)} script${creation.scripts.length === 1 ? "" : "s"} across ${String(phases.length)} phase${phases.length === 1 ? "" : "s"}…`
467
- );
468
- for (const [, scripts] of phases) {
469
- const results = await Promise.all(scripts.map((script) => Promise.resolve(runScript(script, options.destination))));
470
- if (results.includes(false)) {
471
- throw new Error("Script failed — aborting.");
472
- }
473
- }
474
- }
475
- if (creation.suggestions && creation.suggestions.length > 0) {
476
- process.stderr.write("\n");
477
- pail.notice("Next steps:");
478
- for (const suggestion of creation.suggestions) {
479
- process.stderr.write(` ${dim("•")} ${suggestion}
480
- `);
481
- }
482
- }
483
- };
274
+ // Escape text for safe DOM insertion
275
+ function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.textContent; }
484
276
 
485
- const toListEntry = async (template) => {
486
- let description;
487
- try {
488
- const loaded = await template.load();
489
- description = loaded.about?.description;
490
- } catch {
491
- }
492
- return { description, name: template.name, path: template.path, source: template.source };
493
- };
494
- const summarizeVariable = (name, variable) => {
495
- const summary = {
496
- default: variable.default,
497
- name,
498
- order: variable.order,
499
- prompt: variable.prompt,
500
- required: variable.required,
501
- type: variable.type
502
- };
503
- if (variable.type === "enum") {
504
- summary.multiple = variable.multiple;
505
- summary.values = variable.values;
506
- }
507
- return summary;
508
- };
509
- const describeTemplate = async (discovered) => {
510
- const loaded = await discovered.load();
511
- const variables = Object.entries(loaded.options ?? {}).sort(([nameA, a], [nameB, b]) => {
512
- const orderA = a.order ?? 0;
513
- const orderB = b.order ?? 0;
514
- return orderA === orderB ? nameA.localeCompare(nameB) : orderA - orderB;
515
- }).map(([name, variable]) => summarizeVariable(name, variable));
516
- return {
517
- description: loaded.about?.description ?? "",
518
- destination: loaded.destination,
519
- name: discovered.name,
520
- path: discovered.path,
521
- source: discovered.source,
522
- variables
523
- };
524
- };
525
- const printList = (templates) => {
526
- if (templates.length === 0) {
527
- pail.info("No templates found.");
528
- pail.notice("Create one at .vis/templates/<name>.ts (programmatic) or .vis/templates/<name>/ (moon-format with template.yml).");
529
- return;
530
- }
531
- pail.info("Available templates:");
532
- for (const template of templates) {
533
- const tag = dim(`(${template.source})`);
534
- process.stderr.write(` ${bold(cyan(template.name))} ${tag}
535
- `);
536
- }
537
- };
538
- const parsePassthroughOverrides = (extraArguments) => {
539
- const overrides = {};
540
- const remaining = [];
541
- for (const argument of extraArguments) {
542
- if (!argument.startsWith("--")) {
543
- remaining.push(argument);
544
- continue;
545
- }
546
- const equalsIndex = argument.indexOf("=");
547
- if (equalsIndex === -1) {
548
- const key2 = argument.slice(2);
549
- if (key2.startsWith("no-")) {
550
- overrides[key2.slice(3)] = "false";
551
- } else {
552
- overrides[key2] = "true";
553
- }
554
- continue;
555
- }
556
- const key = argument.slice(2, equalsIndex);
557
- const value = argument.slice(equalsIndex + 1);
558
- overrides[key] = value;
559
- }
560
- return { overrides, remaining };
561
- };
562
- const pickInteractive = async (templates) => {
563
- const { createInterface } = await import('node:readline');
564
- const rl = createInterface({ input: process.stdin, output: process.stderr });
565
- try {
566
- process.stderr.write(` ${bold(cyan("vis generate"))} ${dim("— pick a template")}
277
+ // Force-directed layout
278
+ const repulsion = 5000 + data.nodes.length * 150;
279
+ const nodes = data.nodes.map(n => ({
280
+ ...n, x: W/2 + (Math.random()-0.5)*Math.min(W*0.6, 600),
281
+ y: H/2 + (Math.random()-0.5)*Math.min(H*0.6, 400), vx: 0, vy: 0
282
+ }));
283
+ const nodeMap = new Map(nodes.map(n => [n.name, n]));
284
+ const edges = data.edges.map(e => ({
285
+ source: nodeMap.get(e.source), target: nodeMap.get(e.target), type: e.type
286
+ }));
567
287
 
568
- `);
569
- for (const [index, template] of templates.entries()) {
570
- const prefix = bold(cyan(` ${String(index + 1)}.`));
571
- process.stderr.write(`${prefix} ${template.name} ${dim(`(${template.source})`)}
572
- `);
573
- }
574
- return new Promise((resolveValue, reject) => {
575
- rl.question(`
576
- ${dim(`Enter choice (1-${String(templates.length)}):`)} `, (answer) => {
577
- const number_ = Number.parseInt(answer.trim(), 10);
578
- if (Number.isInteger(number_) && number_ >= 1 && number_ <= templates.length) {
579
- resolveValue(templates[number_ - 1].name);
580
- } else {
581
- reject(new Error("Invalid choice."));
582
- }
583
- });
584
- });
585
- } finally {
586
- rl.close();
587
- }
588
- };
589
- const execute = async ({ argument, options, rawUnknown, visConfig, workspaceRoot: wsRoot }) => {
590
- const cwd = options.cwd || wsRoot || process.cwd();
591
- const workspaceRoot = wsRoot ?? cwd;
592
- const generatorConfig = visConfig?.generator;
593
- const args = Array.isArray(argument) ? argument : argument ? [argument] : [];
594
- if (options.list) {
595
- const discovered = discoverTemplates({
596
- extraDirectories: generatorConfig?.templates ?? [],
597
- onWarning: (message) => {
598
- pail.warn(message);
599
- },
600
- workspaceRoot
601
- });
602
- if (options.json) {
603
- const entries = await Promise.all(discovered.map((t) => toListEntry(t)));
604
- process.stdout.write(`${JSON.stringify(entries, null, 2)}
605
- `);
606
- return;
607
- }
608
- printList(discovered);
609
- return;
610
- }
611
- if (options.describe) {
612
- const wanted = args[0];
613
- if (!wanted) {
614
- throw new Error("`--describe` requires a template name. Run `vis generate --list` to see available templates.");
615
- }
616
- const discovered = discoverTemplates({
617
- extraDirectories: generatorConfig?.templates ?? [],
618
- onWarning: (message) => {
619
- pail.warn(message);
620
- },
621
- workspaceRoot
622
- });
623
- const match = discovered.find((t) => t.name === wanted);
624
- if (!match) {
625
- throw new Error(`Template "${wanted}" not found. Run \`vis generate --list\` to see available templates.`);
626
- }
627
- const described = await describeTemplate(match);
628
- if (options.json) {
629
- process.stdout.write(`${JSON.stringify(described, null, 2)}
630
- `);
288
+ for (let iter = 0; iter < 400; iter++) {
289
+ for (let i = 0; i < nodes.length; i++) {
290
+ for (let j = i+1; j < nodes.length; j++) {
291
+ let dx = nodes[j].x - nodes[i].x, dy = nodes[j].y - nodes[i].y;
292
+ let d = Math.sqrt(dx*dx + dy*dy) || 1;
293
+ let f = repulsion / (d * d);
294
+ nodes[i].vx -= dx/d * f; nodes[i].vy -= dy/d * f;
295
+ nodes[j].vx += dx/d * f; nodes[j].vy += dy/d * f;
296
+ }
297
+ }
298
+ edges.forEach(e => {
299
+ if (!e.source || !e.target) {
300
+ return;
301
+ }
302
+ let dx = e.target.x - e.source.x, dy = e.target.y - e.source.y;
303
+ let d = Math.sqrt(dx*dx + dy*dy) || 1;
304
+ let f = (d - 180) * 0.008;
305
+ e.source.vx += dx/d * f; e.source.vy += dy/d * f;
306
+ e.target.vx -= dx/d * f; e.target.vy -= dy/d * f;
307
+ });
308
+ nodes.forEach(n => {
309
+ n.vx += (W/2 - n.x) * 0.001; n.vy += (H/2 - n.y) * 0.001;
310
+ n.x += n.vx * 0.3; n.y += n.vy * 0.3;
311
+ n.vx *= 0.75; n.vy *= 0.75;
312
+ n.x = Math.max(80, Math.min(W-80, n.x));
313
+ n.y = Math.max(40, Math.min(H-40, n.y));
314
+ });
315
+ }
316
+
317
+ // Measure text widths
318
+ const measure = document.createElementNS('http://www.w3.org/2000/svg','text');
319
+ measure.setAttribute('font-size','12'); measure.setAttribute('font-weight','600');
320
+ measure.setAttribute('font-family','system-ui');
321
+ svg.appendChild(measure);
322
+ const widths = {};
323
+ nodes.forEach(n => { measure.textContent = n.name; widths[n.name] = measure.getComputedTextLength(); });
324
+ svg.removeChild(measure);
325
+
326
+ // Render edges
327
+ edges.forEach(e => {
328
+ if (!e.source || !e.target) {
631
329
  return;
632
- }
633
- pail.info(`Template: ${bold(cyan(described.name))} ${dim(`(${described.source})`)}`);
634
- if (described.description) {
635
- pail.info(described.description);
636
- }
637
- if (described.destination) {
638
- pail.info(`Destination: ${dim(described.destination)}`);
639
- }
640
- if (described.variables.length === 0) {
641
- pail.info("No variables.");
642
- } else {
643
- pail.info("Variables:");
644
- for (const variable of described.variables) {
645
- const flags = [variable.type];
646
- if (variable.required) flags.push("required");
647
- if (variable.default !== void 0) flags.push(`default=${JSON.stringify(variable.default)}`);
648
- if (variable.values) flags.push(`values=${variable.values.join("|")}`);
649
- process.stderr.write(` ${bold(cyan(variable.name))} ${dim(`(${flags.join(", ")})`)}
650
- `);
651
- }
652
- }
653
- return;
654
- }
655
- let passthrough = [...rawUnknown ?? []];
656
- if (passthrough.length === 0) {
657
- const rawArgv = process.argv.slice(2);
658
- const argvDashIndex = rawArgv.indexOf("--");
659
- if (argvDashIndex !== -1) {
660
- passthrough = rawArgv.slice(argvDashIndex + 1);
661
- }
662
- }
663
- const legacyDashIndex = args.indexOf("--");
664
- const legacyExtras = legacyDashIndex === -1 ? [] : args.slice(legacyDashIndex + 1);
665
- const ownArgs = legacyDashIndex === -1 ? args : args.slice(0, legacyDashIndex);
666
- const { overrides } = parsePassthroughOverrides([...legacyExtras, ...passthrough]);
667
- let template;
668
- let templateName;
669
- let templateDestination;
670
- const input = ownArgs[0];
671
- let remoteCleanup;
672
- if (input && isRemoteSource(input)) {
673
- const fetched = await fetchRemoteTemplate(input, {
674
- auth: generatorConfig?.auth,
675
- preferOffline: Boolean(options.preferOffline) || generatorConfig?.preferOffline
676
- });
677
- remoteCleanup = fetched.cleanup;
678
- try {
679
- const discovered = discoverTemplates({ extraDirectories: [fetched.directory], workspaceRoot });
680
- const remoteTemplate = discovered.find((t) => t.path.startsWith(fetched.directory));
681
- if (!remoteTemplate) {
682
- throw new Error(`Downloaded template at ${fetched.directory} contains no template.yml or *.ts entrypoint.`);
683
- }
684
- template = await remoteTemplate.load();
685
- templateName = remoteTemplate.name;
686
- templateDestination = template.destination;
687
- } catch (error) {
688
- remoteCleanup();
689
- remoteCleanup = void 0;
690
- throw error;
691
- }
692
- } else {
693
- const discovered = discoverTemplates({
694
- extraDirectories: generatorConfig?.templates ?? [],
695
- onWarning: (message) => {
696
- pail.warn(message);
697
- },
698
- workspaceRoot
699
- });
700
- if (discovered.length === 0) {
701
- throw new Error("No templates found. Create one at .vis/templates/<name>.ts or .vis/templates/<name>/template.yml.");
702
- }
703
- let wanted;
704
- if (input) {
705
- wanted = input;
706
- } else if (options.noInteractive || !process.stdin.isTTY) {
707
- throw new Error("No template specified. Pass a template name (see `vis generate --list`) or run interactively in a terminal.");
708
- } else {
709
- wanted = await pickInteractive(discovered);
710
- }
711
- const match = discovered.find((t) => t.name === wanted);
712
- if (!match) {
713
- throw new Error(`Template "${wanted}" not found. Run 'vis generate --list' to see available templates.`);
714
- }
715
- template = await match.load();
716
- templateName = match.name;
717
- templateDestination = template.destination;
718
330
  }
719
- const toFlag = options.to;
720
- const dryRun = Boolean(options.dryRun);
721
- const force = Boolean(options.force);
722
- const useDefaults = Boolean(options.defaults);
723
- const skipScripts = Boolean(options.skipScripts);
724
- const isInteractive = !options.noInteractive && Boolean(process.stdin.isTTY) && !useDefaults;
725
- let destinationInput;
726
- if (toFlag) {
727
- destinationInput = toFlag;
728
- } else if (templateDestination) {
729
- destinationInput = templateDestination;
730
- } else {
731
- destinationInput = ".";
732
- }
733
- const destination = isAbsolute(destinationInput) ? destinationInput : resolve(cwd, destinationInput);
734
- pail.info(`Template: ${bold(cyan(templateName))}`);
735
- pail.info(`Target: ${dim(destination)}`);
736
- process.stderr.write("\n");
737
- const collectedOptions = await collectOptions({
738
- defaults: useDefaults,
739
- interactive: isInteractive,
740
- overrides,
741
- variables: template.options ?? {}
742
- });
743
- try {
744
- await runTemplate(template, {
745
- cwd,
746
- destination,
747
- dryRun,
748
- force,
749
- options: collectedOptions,
750
- skipScripts,
751
- workspaceRoot
752
- });
753
- if (!dryRun) {
754
- process.stderr.write("\n");
755
- pail.success(`Template '${templateName}' applied.`);
331
+ const sw = (widths[e.source.name]||80)/2 + 12;
332
+ const tw = (widths[e.target.name]||80)/2 + 12;
333
+ const dx = e.target.x - e.source.x, dy = e.target.y - e.source.y;
334
+ const d = Math.sqrt(dx*dx+dy*dy)||1;
335
+ const x1 = e.source.x + dx/d*sw, y1 = e.source.y + dy/d*14;
336
+ const x2 = e.target.x - dx/d*tw, y2 = e.target.y - dy/d*14;
337
+ const line = document.createElementNS('http://www.w3.org/2000/svg','line');
338
+ line.setAttribute('x1',x1); line.setAttribute('y1',y1);
339
+ line.setAttribute('x2',x2); line.setAttribute('y2',y2);
340
+ line.setAttribute('class','edge');
341
+ const edgeColors = { implicit: '#475569', devDependency: '#888888', peerDependency: '#CC8800' };
342
+ line.setAttribute('stroke', edgeColors[e.type] || '#64748b');
343
+ line.setAttribute('stroke-width', '1.5');
344
+ if (e.type === 'implicit' || e.type === 'peerDependency') {
345
+ line.setAttribute('stroke-dasharray', '6,4');
346
+ }
347
+ if (e.type === 'devDependency') {
348
+ line.setAttribute('stroke-dasharray', '3,3');
349
+ }
350
+ svg.appendChild(line);
351
+ });
352
+
353
+ // Render nodes
354
+ nodes.forEach(n => {
355
+ const w = (widths[n.name]||80) + 24;
356
+ const h = 32;
357
+ const isApp = n.type === 'application';
358
+ const fill = isApp ? '#fbbf24' : '#38bdf8';
359
+ const textFill = '#0f172a';
360
+ const stroke = isApp ? '#f59e0b' : '#0284c7';
361
+
362
+ const g = document.createElementNS('http://www.w3.org/2000/svg','g');
363
+ g.setAttribute('class','node');
364
+ g.setAttribute('transform','translate('+(n.x - w/2)+','+(n.y - h/2)+')');
365
+ const rect = document.createElementNS('http://www.w3.org/2000/svg','rect');
366
+ rect.setAttribute('width', w); rect.setAttribute('height', h);
367
+ rect.setAttribute('fill', fill); rect.setAttribute('stroke', stroke); rect.setAttribute('stroke-width','1.5');
368
+ g.appendChild(rect);
369
+ const text = document.createElementNS('http://www.w3.org/2000/svg','text');
370
+ text.setAttribute('x', w/2); text.setAttribute('y', h/2 + 4.5);
371
+ text.setAttribute('text-anchor','middle'); text.setAttribute('fill', textFill);
372
+ text.textContent = n.name;
373
+ g.appendChild(text);
374
+
375
+ g.addEventListener('mouseenter', (ev) => {
376
+ const deps = depMap[n.name] || [];
377
+ const rdeps = rdepMap[n.name] || [];
378
+
379
+ // Build tooltip using safe DOM methods
380
+ tooltip.textContent = '';
381
+
382
+ const heading = document.createElement('h3');
383
+ heading.textContent = n.name;
384
+ const badge = document.createElement('span');
385
+ badge.className = 'type-badge';
386
+ badge.style.background = fill;
387
+ badge.style.color = '#0f172a';
388
+ badge.textContent = n.type;
389
+ heading.appendChild(badge);
390
+ tooltip.appendChild(heading);
391
+
392
+ if (deps.length) {
393
+ const label = document.createElement('div');
394
+ label.className = 'dep-section';
395
+ label.textContent = 'Depends on:';
396
+ tooltip.appendChild(label);
397
+ const ul = document.createElement('ul');
398
+ deps.forEach(d => { const li = document.createElement('li'); li.textContent = d; ul.appendChild(li); });
399
+ tooltip.appendChild(ul);
400
+ }
401
+ if (rdeps.length) {
402
+ const label = document.createElement('div');
403
+ label.className = 'dep-section';
404
+ label.textContent = 'Required by:';
405
+ tooltip.appendChild(label);
406
+ const ul = document.createElement('ul');
407
+ rdeps.forEach(d => { const li = document.createElement('li'); li.textContent = d; ul.appendChild(li); });
408
+ tooltip.appendChild(ul);
409
+ }
410
+ if (!deps.length && !rdeps.length) {
411
+ const empty = document.createElement('div');
412
+ empty.style.marginTop = '6px';
413
+ empty.style.color = '#64748b';
414
+ empty.textContent = 'No dependencies';
415
+ tooltip.appendChild(empty);
756
416
  }
757
- } finally {
758
- remoteCleanup?.();
759
- }
760
- };
761
417
 
762
- export { execute as default };
418
+ tooltip.style.display = 'block';
419
+ const rect = tooltip.getBoundingClientRect();
420
+ tooltip.style.left = Math.min(ev.clientX + 12, W - rect.width - 12) + 'px';
421
+ tooltip.style.top = Math.min(ev.clientY + 12, H - rect.height - 12) + 'px';
422
+ });
423
+ g.addEventListener('mouseleave', () => { tooltip.style.display = 'none'; });
424
+ svg.appendChild(g);
425
+ });
426
+ <\/script>
427
+ </body>
428
+ </html>`])),s.apps,s.libs,s.edges.length,JSON.stringify(s).replaceAll("</",String.raw`<\/`))},"projectGraphToHtml"),Ve=D(async({logger:o,options:e,visConfig:l,workspaceRoot:d})=>{if(!d)throw new Error("Could not determine workspace root. Run this command inside a monorepo.");const c=d,{packageJsons:s,workspace:u}=xe(c,l),a=be(c,u,s),y=!!process.stdout.isTTY&&!ye,g=e.format??(y?"tui":"ascii"),v=e.output,h=e.depth??1/0;let f;switch(g){case"dot":{f=ce(a);break}case"html":{f=Ge(a);break}case"json":{f=JSON.stringify(He(a),void 0,2);break}case"tui":{if(!y){f=K(a,h);break}const x=l?.tui?.autoExit===!0?3:typeof l?.tui?.autoExit=="number"?l.tui.autoExit:0;process.stdin.isTTY&&typeof process.stdin.setRawMode=="function"&&(process.stdin.setRawMode(!0),process.stdin.ref(),process.stdin.resume());const C=setInterval(()=>{},1e3),M=new Me(a);await me(we.createElement(Re,{autoExitSeconds:x,store:M}),{alternateScreen:!0,exitOnCtrlC:!1,interactive:!0,patchConsole:!0}).waitUntilExit(),clearInterval(C);return}default:f=K(a,h)}v?(se(v,f,"utf8"),o.info(`Graph written to ${v}`)):o.info(f)},"execute");export{Ve as default};