@vladimirshefer/git-stats 0.0.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +510 -504
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -31,18 +31,46 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/index.ts
32
32
  var index_exports = {};
33
33
  __export(index_exports, {
34
- distinctCount: () => distinctCount
34
+ dataDir: () => dataDir,
35
+ progress: () => progress,
36
+ runScan1: () => runScan1
35
37
  });
36
38
  module.exports = __toCommonJS(index_exports);
37
- var import_child_process2 = require("child_process");
39
+ var fs5 = __toESM(require("fs"));
40
+ var path5 = __toESM(require("path"));
41
+ var readline = __toESM(require("readline"));
42
+
43
+ // src/output/report_template.ts
44
+ var path = __toESM(require("path"));
45
+ var fs = __toESM(require("fs"));
46
+
47
+ // ../html-ui/dist/index.html
48
+ var dist_default = '<!DOCTYPE html>\n<html lang="en">\n<head>\n <meta charset="UTF-8" />\n <meta name="viewport" content="width=device-width, initial-scale=1.0" />\n <script src="https://cdn.plot.ly/plotly-3.3.0.min.js" charset="utf-8"></script>\n <title>Git Blame Statistics</title>\n <style>/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */\n@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-white:#fff;--spacing:.25rem;--container-4xl:56rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--font-weight-normal:400;--font-weight-semibold:600;--radius-md:.375rem;--radius-lg:.5rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.absolute{position:absolute}.relative{position:relative}.z-10{z-index:10}.float-right{float:right}.m-0{margin:calc(var(--spacing)*0)}.mx-auto{margin-inline:auto}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-8{margin-top:calc(var(--spacing)*8)}.mr-2{margin-right:calc(var(--spacing)*2)}.mb-1\\.5{margin-bottom:calc(var(--spacing)*1.5)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.block{display:block}.flex{display:flex}.grid{display:grid}.h-4{height:calc(var(--spacing)*4)}.max-h-64{max-height:calc(var(--spacing)*64)}.max-h-80{max-height:calc(var(--spacing)*80)}.w-4{width:calc(var(--spacing)*4)}.w-full{width:100%}.max-w-4xl{max-width:var(--container-4xl)}.flex-1{flex:1}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-\\[repeat\\(auto-fit\\,minmax\\(220px\\,1fr\\)\\)\\]{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.items-center{align-items:center}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-600{background-color:var(--color-gray-600)}.bg-white{background-color:var(--color-white)}.p-2{padding:calc(var(--spacing)*2)}.p-4{padding:calc(var(--spacing)*4)}.px-3{padding-inline:calc(var(--spacing)*3)}.py-1\\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-6{padding-block:calc(var(--spacing)*6)}.pb-2\\.5{padding-bottom:calc(var(--spacing)*2.5)}.text-center{text-align:center}.text-left{text-align:left}.font-sans{font-family:var(--font-sans)}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.text-blue-600{color:var(--color-blue-600)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-white{color:var(--color-white)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media (hover:hover){.hover\\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\\:bg-gray-700:hover{background-color:var(--color-gray-700)}}.focus\\:border-blue-500:focus{border-color:var(--color-blue-500)}.focus\\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.focus\\:ring-gray-500:focus{--tw-ring-color:var(--color-gray-500)}.focus\\:outline-none:focus{--tw-outline-style:none;outline-style:none}@media (min-width:48rem){.md\\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}</style>\n</head>\n<script>\n try {\n window.RAW_DATASET =\n __DATASET_JSON__\n } catch (e) {\n window.RAW_DATASET = [[1, 2, 3, 4, 5, 6, 7, 8, 9]]\n }\n</script>\n<body class="font-sans m-0 bg-gray-50 text-gray-900">\n <div id="root"></div>\n <script>(()=>{var Y,g,he,ze,U,fe,ve,ge,ye,oe,te,ne,Qe,Q={},G=[],Ge=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,J=Array.isArray;function E(t,e){for(var n in e)t[n]=e[n];return t}function _e(t){t&&t.parentNode&&t.parentNode.removeChild(t)}function Xe(t,e,n){var _,s,o,l={};for(o in e)o=="key"?_=e[o]:o=="ref"?s=e[o]:l[o]=e[o];if(arguments.length>2&&(l.children=arguments.length>3?Y.call(arguments,2):n),typeof t=="function"&&t.defaultProps!=null)for(o in t.defaultProps)l[o]===void 0&&(l[o]=t.defaultProps[o]);return q(t,l,_,s,null)}function q(t,e,n,_,s){var o={type:t,props:e,key:n,ref:_,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:s==null?++he:s,__i:-1,__u:0};return s==null&&g.vnode!=null&&g.vnode(o),o}function H(t){return t.children}function z(t,e){this.props=t,this.context=e}function F(t,e){if(e==null)return t.__?F(t.__,t.__i+1):null;for(var n;e<t.__k.length;e++)if((n=t.__k[e])!=null&&n.__e!=null)return n.__e;return typeof t.type=="function"?F(t):null}function Ye(t){if(t.__P&&t.__d){var e=t.__v,n=e.__e,_=[],s=[],o=E({},e);o.__v=e.__v+1,g.vnode&&g.vnode(o),se(t.__P,o,e,t.__n,t.__P.namespaceURI,32&e.__u?[n]:null,_,n==null?F(e):n,!!(32&e.__u),s),o.__v=e.__v,o.__.__k[o.__i]=o,ke(_,o,s),e.__e=e.__=null,o.__e!=n&&be(o)}}function be(t){if((t=t.__)!=null&&t.__c!=null)return t.__e=t.__c.base=null,t.__k.some(function(e){if(e!=null&&e.__e!=null)return t.__e=t.__c.base=e.__e}),be(t)}function pe(t){(!t.__d&&(t.__d=!0)&&U.push(t)&&!X.__r++||fe!=g.debounceRendering)&&((fe=g.debounceRendering)||ve)(X)}function X(){for(var t,e=1;U.length;)U.length>e&&U.sort(ge),t=U.shift(),e=U.length,Ye(t);X.__r=0}function xe(t,e,n,_,s,o,l,a,f,i,u){var r,p,c,m,y,b,v,h=_&&_.__k||G,C=e.length;for(f=Je(n,e,h,f,C),r=0;r<C;r++)(c=n.__k[r])!=null&&(p=c.__i!=-1&&h[c.__i]||Q,c.__i=r,b=se(t,c,p,s,o,l,a,f,i,u),m=c.__e,c.ref&&p.ref!=c.ref&&(p.ref&&le(p.ref,null,c),u.push(c.ref,c.__c||m,c)),y==null&&m!=null&&(y=m),(v=!!(4&c.__u))||p.__k===c.__k?f=we(c,f,t,v):typeof c.type=="function"&&b!==void 0?f=b:m&&(f=m.nextSibling),c.__u&=-7);return n.__e=y,f}function Je(t,e,n,_,s){var o,l,a,f,i,u=n.length,r=u,p=0;for(t.__k=new Array(s),o=0;o<s;o++)(l=e[o])!=null&&typeof l!="boolean"&&typeof l!="function"?(typeof l=="string"||typeof l=="number"||typeof l=="bigint"||l.constructor==String?l=t.__k[o]=q(null,l,null,null,null):J(l)?l=t.__k[o]=q(H,{children:l},null,null,null):l.constructor===void 0&&l.__b>0?l=t.__k[o]=q(l.type,l.props,l.key,l.ref?l.ref:null,l.__v):t.__k[o]=l,f=o+p,l.__=t,l.__b=t.__b+1,a=null,(i=l.__i=Ke(l,n,f,r))!=-1&&(r--,(a=n[i])&&(a.__u|=2)),a==null||a.__v==null?(i==-1&&(s>u?p--:s<u&&p++),typeof l.type!="function"&&(l.__u|=4)):i!=f&&(i==f-1?p--:i==f+1?p++:(i>f?p--:p++,l.__u|=4))):t.__k[o]=null;if(r)for(o=0;o<u;o++)(a=n[o])!=null&&(2&a.__u)==0&&(a.__e==_&&(_=F(a)),Ne(a,a));return _}function we(t,e,n,_){var s,o;if(typeof t.type=="function"){for(s=t.__k,o=0;s&&o<s.length;o++)s[o]&&(s[o].__=t,e=we(s[o],e,n,_));return e}t.__e!=e&&(_&&(e&&t.type&&!e.parentNode&&(e=F(t)),n.insertBefore(t.__e,e||null)),e=t.__e);do e=e&&e.nextSibling;while(e!=null&&e.nodeType==8);return e}function Ke(t,e,n,_){var s,o,l,a=t.key,f=t.type,i=e[n],u=i!=null&&(2&i.__u)==0;if(i===null&&a==null||u&&a==i.key&&f==i.type)return n;if(_>(u?1:0)){for(s=n-1,o=n+1;s>=0||o<e.length;)if((i=e[l=s>=0?s--:o++])!=null&&(2&i.__u)==0&&a==i.key&&f==i.type)return l}return-1}function de(t,e,n){e[0]=="-"?t.setProperty(e,n==null?"":n):t[e]=n==null?"":typeof n!="number"||Ge.test(e)?n:n+"px"}function V(t,e,n,_,s){var o,l;e:if(e=="style")if(typeof n=="string")t.style.cssText=n;else{if(typeof _=="string"&&(t.style.cssText=_=""),_)for(e in _)n&&e in n||de(t.style,e,"");if(n)for(e in n)_&&n[e]==_[e]||de(t.style,e,n[e])}else if(e[0]=="o"&&e[1]=="n")o=e!=(e=e.replace(ye,"$1")),l=e.toLowerCase(),e=l in t||e=="onFocusOut"||e=="onFocusIn"?l.slice(2):e.slice(2),t.l||(t.l={}),t.l[e+o]=n,n?_?n.u=_.u:(n.u=oe,t.addEventListener(e,o?ne:te,o)):t.removeEventListener(e,o?ne:te,o);else{if(s=="http://www.w3.org/2000/svg")e=e.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if(e!="width"&&e!="height"&&e!="href"&&e!="list"&&e!="form"&&e!="tabIndex"&&e!="download"&&e!="rowSpan"&&e!="colSpan"&&e!="role"&&e!="popover"&&e in t)try{t[e]=n==null?"":n;break e}catch(a){}typeof n=="function"||(n==null||n===!1&&e[4]!="-"?t.removeAttribute(e):t.setAttribute(e,e=="popover"&&n==1?"":n))}}function me(t){return function(e){if(this.l){var n=this.l[e.type+t];if(e.t==null)e.t=oe++;else if(e.t<n.u)return;return n(g.event?g.event(e):e)}}}function se(t,e,n,_,s,o,l,a,f,i){var u,r,p,c,m,y,b,v,h,C,N,A,D,j,ee,S=e.type;if(e.constructor!==void 0)return null;128&n.__u&&(f=!!(32&n.__u),o=[a=e.__e=n.__e]),(u=g.__b)&&u(e);e:if(typeof S=="function")try{if(v=e.props,h="prototype"in S&&S.prototype.render,C=(u=S.contextType)&&_[u.__c],N=u?C?C.props.value:u.__:_,n.__c?b=(r=e.__c=n.__c).__=r.__E:(h?e.__c=r=new S(v,N):(e.__c=r=new z(v,N),r.constructor=S,r.render=et),C&&C.sub(r),r.state||(r.state={}),r.__n=_,p=r.__d=!0,r.__h=[],r._sb=[]),h&&r.__s==null&&(r.__s=r.state),h&&S.getDerivedStateFromProps!=null&&(r.__s==r.state&&(r.__s=E({},r.__s)),E(r.__s,S.getDerivedStateFromProps(v,r.__s))),c=r.props,m=r.state,r.__v=e,p)h&&S.getDerivedStateFromProps==null&&r.componentWillMount!=null&&r.componentWillMount(),h&&r.componentDidMount!=null&&r.__h.push(r.componentDidMount);else{if(h&&S.getDerivedStateFromProps==null&&v!==c&&r.componentWillReceiveProps!=null&&r.componentWillReceiveProps(v,N),e.__v==n.__v||!r.__e&&r.shouldComponentUpdate!=null&&r.shouldComponentUpdate(v,r.__s,N)===!1){e.__v!=n.__v&&(r.props=v,r.state=r.__s,r.__d=!1),e.__e=n.__e,e.__k=n.__k,e.__k.some(function(L){L&&(L.__=e)}),G.push.apply(r.__h,r._sb),r._sb=[],r.__h.length&&l.push(r);break e}r.componentWillUpdate!=null&&r.componentWillUpdate(v,r.__s,N),h&&r.componentDidUpdate!=null&&r.__h.push(function(){r.componentDidUpdate(c,m,y)})}if(r.context=N,r.props=v,r.__P=t,r.__e=!1,A=g.__r,D=0,h)r.state=r.__s,r.__d=!1,A&&A(e),u=r.render(r.props,r.state,r.context),G.push.apply(r.__h,r._sb),r._sb=[];else do r.__d=!1,A&&A(e),u=r.render(r.props,r.state,r.context),r.state=r.__s;while(r.__d&&++D<25);r.state=r.__s,r.getChildContext!=null&&(_=E(E({},_),r.getChildContext())),h&&!p&&r.getSnapshotBeforeUpdate!=null&&(y=r.getSnapshotBeforeUpdate(c,m)),j=u!=null&&u.type===H&&u.key==null?Ce(u.props.children):u,a=xe(t,J(j)?j:[j],e,n,_,s,o,l,a,f,i),r.base=e.__e,e.__u&=-161,r.__h.length&&l.push(r),b&&(r.__E=r.__=null)}catch(L){if(e.__v=null,f||o!=null)if(L.then){for(e.__u|=f?160:128;a&&a.nodeType==8&&a.nextSibling;)a=a.nextSibling;o[o.indexOf(a)]=null,e.__e=a}else{for(ee=o.length;ee--;)_e(o[ee]);re(e)}else e.__e=n.__e,e.__k=n.__k,L.then||re(e);g.__e(L,e,n)}else o==null&&e.__v==n.__v?(e.__k=n.__k,e.__e=n.__e):a=e.__e=Ze(n.__e,e,n,_,s,o,l,f,i);return(u=g.diffed)&&u(e),128&e.__u?void 0:a}function re(t){t&&(t.__c&&(t.__c.__e=!0),t.__k&&t.__k.some(re))}function ke(t,e,n){for(var _=0;_<n.length;_++)le(n[_],n[++_],n[++_]);g.__c&&g.__c(e,t),t.some(function(s){try{t=s.__h,s.__h=[],t.some(function(o){o.call(s)})}catch(o){g.__e(o,s.__v)}})}function Ce(t){return typeof t!="object"||t==null||t.__b>0?t:J(t)?t.map(Ce):E({},t)}function Ze(t,e,n,_,s,o,l,a,f){var i,u,r,p,c,m,y,b=n.props||Q,v=e.props,h=e.type;if(h=="svg"?s="http://www.w3.org/2000/svg":h=="math"?s="http://www.w3.org/1998/Math/MathML":s||(s="http://www.w3.org/1999/xhtml"),o!=null){for(i=0;i<o.length;i++)if((c=o[i])&&"setAttribute"in c==!!h&&(h?c.localName==h:c.nodeType==3)){t=c,o[i]=null;break}}if(t==null){if(h==null)return document.createTextNode(v);t=document.createElementNS(s,h,v.is&&v),a&&(g.__m&&g.__m(e,o),a=!1),o=null}if(h==null)b===v||a&&t.data==v||(t.data=v);else{if(o=o&&Y.call(t.childNodes),!a&&o!=null)for(b={},i=0;i<t.attributes.length;i++)b[(c=t.attributes[i]).name]=c.value;for(i in b)c=b[i],i=="dangerouslySetInnerHTML"?r=c:i=="children"||i in v||i=="value"&&"defaultValue"in v||i=="checked"&&"defaultChecked"in v||V(t,i,null,c,s);for(i in v)c=v[i],i=="children"?p=c:i=="dangerouslySetInnerHTML"?u=c:i=="value"?m=c:i=="checked"?y=c:a&&typeof c!="function"||b[i]===c||V(t,i,c,b[i],s);if(u)a||r&&(u.__html==r.__html||u.__html==t.innerHTML)||(t.innerHTML=u.__html),e.__k=[];else if(r&&(t.innerHTML=""),xe(e.type=="template"?t.content:t,J(p)?p:[p],e,n,_,h=="foreignObject"?"http://www.w3.org/1999/xhtml":s,o,l,o?o[0]:n.__k&&F(n,0),a,f),o!=null)for(i=o.length;i--;)_e(o[i]);a||(i="value",h=="progress"&&m==null?t.removeAttribute("value"):m!=null&&(m!==t[i]||h=="progress"&&!m||h=="option"&&m!=b[i])&&V(t,i,m,b[i],s),i="checked",y!=null&&y!=t[i]&&V(t,i,y,b[i],s))}return t}function le(t,e,n){try{if(typeof t=="function"){var _=typeof t.__u=="function";_&&t.__u(),_&&e==null||(t.__u=t(e))}else t.current=e}catch(s){g.__e(s,n)}}function Ne(t,e,n){var _,s;if(g.unmount&&g.unmount(t),(_=t.ref)&&(_.current&&_.current!=t.__e||le(_,null,e)),(_=t.__c)!=null){if(_.componentWillUnmount)try{_.componentWillUnmount()}catch(o){g.__e(o,e)}_.base=_.__P=null}if(_=t.__k)for(s=0;s<_.length;s++)_[s]&&Ne(_[s],e,n||typeof t.type!="function");n||_e(t.__e),t.__c=t.__=t.__e=void 0}function et(t,e,n){return this.constructor(t,n)}function Se(t,e,n){var _,s,o,l;e==document&&(e=document.documentElement),g.__&&g.__(t,e),s=(_=typeof n=="function")?null:n&&n.__k||e.__k,o=[],l=[],se(e,t=(!_&&n||e).__k=Xe(H,null,[t]),s||Q,Q,e.namespaceURI,!_&&n?[n]:s?null:e.firstChild?Y.call(e.childNodes):null,o,!_&&n?n:s?s.__e:e.firstChild,_,l),ke(o,t,l)}Y=G.slice,g={__e:function(t,e,n,_){for(var s,o,l;e=e.__;)if((s=e.__c)&&!s.__)try{if((o=s.constructor)&&o.getDerivedStateFromError!=null&&(s.setState(o.getDerivedStateFromError(t)),l=s.__d),s.componentDidCatch!=null&&(s.componentDidCatch(t,_||{}),l=s.__d),l)return s.__E=s}catch(a){t=a}throw t}},he=0,ze=function(t){return t!=null&&t.constructor===void 0},z.prototype.setState=function(t,e){var n;n=this.__s!=null&&this.__s!=this.state?this.__s:this.__s=E({},this.state),typeof t=="function"&&(t=t(E({},n),this.props)),t&&E(n,t),t!=null&&this.__v&&(e&&this._sb.push(e),pe(this))},z.prototype.forceUpdate=function(t){this.__v&&(this.__e=!0,t&&this.__h.push(t),pe(this))},z.prototype.render=H,U=[],ve=typeof Promise=="function"?Promise.prototype.then.bind(Promise.resolve()):setTimeout,ge=function(t,e){return t.__v.__b-e.__v.__b},X.__r=0,ye=/(PointerCapture)$|Capture$/i,oe=0,te=me(!1),ne=me(!0),Qe=0;var I,x,ie,Me,Z=0,Fe=[],w=g,Ae=w.__b,Ee=w.__r,Te=w.diffed,Pe=w.__c,Ue=w.unmount,De=w.__;function ue(t,e){w.__h&&w.__h(x,t,Z||e),Z=0;var n=x.__H||(x.__H={__:[],__h:[]});return t>=n.__.length&&n.__.push({}),n.__[t]}function B(t){return Z=1,tt(Re,t)}function tt(t,e,n){var _=ue(I++,2);if(_.t=t,!_.__c&&(_.__=[n?n(e):Re(void 0,e),function(a){var f=_.__N?_.__N[0]:_.__[0],i=_.t(f,a);f!==i&&(_.__N=[i,_.__[1]],_.__c.setState({}))}],_.__c=x,!x.__f)){var s=function(a,f,i){if(!_.__c.__H)return!0;var u=_.__c.__H.__.filter(function(p){return p.__c});if(u.every(function(p){return!p.__N}))return!o||o.call(this,a,f,i);var r=_.__c.props!==a;return u.some(function(p){if(p.__N){var c=p.__[0];p.__=p.__N,p.__N=void 0,c!==p.__[0]&&(r=!0)}}),o&&o.call(this,a,f,i)||r};x.__f=!0;var o=x.shouldComponentUpdate,l=x.componentWillUpdate;x.componentWillUpdate=function(a,f,i){if(this.__e){var u=o;o=void 0,s(a,f,i),o=u}l&&l.call(this,a,f,i)},x.shouldComponentUpdate=s}return _.__N||_.__}function R(t,e){var n=ue(I++,3);!w.__s&&He(n.__H,e)&&(n.__=t,n.u=e,x.__H.__h.push(n))}function O(t){return Z=5,k(function(){return{current:t}},[])}function k(t,e){var n=ue(I++,7);return He(n.__H,e)&&(n.__=t(),n.__H=e,n.__h=t),n.__}function nt(){for(var t;t=Fe.shift();){var e=t.__H;if(t.__P&&e)try{e.__h.some(K),e.__h.some(ae),e.__h=[]}catch(n){e.__h=[],w.__e(n,t.__v)}}}w.__b=function(t){x=null,Ae&&Ae(t)},w.__=function(t,e){t&&e.__k&&e.__k.__m&&(t.__m=e.__k.__m),De&&De(t,e)},w.__r=function(t){Ee&&Ee(t),I=0;var e=(x=t.__c).__H;e&&(ie===x?(e.__h=[],x.__h=[],e.__.some(function(n){n.__N&&(n.__=n.__N),n.u=n.__N=void 0})):(e.__h.some(K),e.__h.some(ae),e.__h=[],I=0)),ie=x},w.diffed=function(t){Te&&Te(t);var e=t.__c;e&&e.__H&&(e.__H.__h.length&&(Fe.push(e)!==1&&Me===w.requestAnimationFrame||((Me=w.requestAnimationFrame)||rt)(nt)),e.__H.__.some(function(n){n.u&&(n.__H=n.u),n.u=void 0})),ie=x=null},w.__c=function(t,e){e.some(function(n){try{n.__h.some(K),n.__h=n.__h.filter(function(_){return!_.__||ae(_)})}catch(_){e.some(function(s){s.__h&&(s.__h=[])}),e=[],w.__e(_,n.__v)}}),Pe&&Pe(t,e)},w.unmount=function(t){Ue&&Ue(t);var e,n=t.__c;n&&n.__H&&(n.__H.__.some(function(_){try{K(_)}catch(s){e=s}}),n.__H=void 0,e&&w.__e(e,n.__v))};var Le=typeof requestAnimationFrame=="function";function rt(t){var e,n=function(){clearTimeout(_),Le&&cancelAnimationFrame(e),setTimeout(t)},_=setTimeout(n,35);Le&&(e=requestAnimationFrame(n))}function K(t){var e=x,n=t.__c;typeof n=="function"&&(t.__c=void 0,n()),x=e}function ae(t){var e=x;t.__c=t.__(),x=e}function He(t,e){return!t||t.length!==e.length||e.some(function(n,_){return n!==t[_]})}function Re(t,e){return typeof e=="function"?e(t):e}var T=window==null?void 0:window.RAW_DATASET,P=["author","days_bucket","lang","clusterPath","repoName","count"],M=T[0].length-1,W=Array(M).fill(-1).map((t,e)=>e),ot=W.map(t=>{var n;return typeof((n=T==null?void 0:T[0])==null?void 0:n[t])=="number"?(_,s)=>(_||0)-(s||0):(_,s)=>String(_).localeCompare(String(s))}),ce=W.map(t=>_t(T,t).sort(ot[t]));function _t(t,e){let n=new Set(t.map(_=>_[e]));return Array.from(n)}var Oe=P.indexOf("clusterPath"),Ie=P.indexOf("repoName");var st=0,dt=Array.isArray;function d(t,e,n,_,s,o){e||(e={});var l,a,f=e;if("ref"in f)for(a in f={},e)a=="ref"?l=e[a]:f[a]=e[a];var i={type:t,props:f,key:n,ref:l,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:--st,__i:-1,__u:0,__source:s,__self:o};if(typeof t=="function"&&(l=t.defaultProps))for(a in l)f[a]===void 0&&(f[a]=l[a]);return g.vnode&&g.vnode(i),i}function Be({label:t,values:e,selectedSet:n,onChange:_}){let[s,o]=B(!1),[l,a]=B(""),f=O(null);R(()=>{function c(m){f.current&&!f.current.contains(m.target)&&o(!1)}return document.addEventListener("mousedown",c),()=>document.removeEventListener("mousedown",c)},[]);let i=k(()=>{if(!l)return e;let c=l.toLowerCase();return e.filter(m=>String(m).toLowerCase().includes(c))},[e,l]),u=n.size,r=e.length,p=c=>{let m=new Set(n);m.has(c)?m.delete(c):m.add(c),_(m)};return d("div",{ref:f,className:"relative",children:[d("label",{className:"block font-semibold mb-1.5",children:[t," ",d("span",{className:"text-gray-600 font-normal",children:["(",u,"/",r,")"]})]}),d("button",{type:"button",onClick:()=>o(!s),className:"w-full px-3 py-2 text-left bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500",children:[d("span",{className:"text-gray-700",children:u===0?"Select...":u===r?"All selected":`${u} selected`}),d("span",{className:"float-right",children:"\\u25BC"})]}),s&&d("div",{className:"absolute z-10 w-full mt-1 bg-white border border-gray-300 rounded-md shadow-lg max-h-80 overflow-hidden",children:[d("div",{className:"p-2 border-b border-gray-200",children:[d("input",{type:"text",placeholder:"Search...",value:l,onChange:c=>a(c.target.value),className:"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500",onClick:c=>c.stopPropagation()}),d("div",{className:"flex gap-2 mt-2",children:[d("button",{type:"button",onClick:()=>_(new Set(e.map(String))),className:"flex-1 px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500",children:"Select All"}),d("button",{type:"button",onClick:()=>_(new Set),className:"flex-1 px-3 py-1.5 text-sm bg-gray-600 text-white rounded-md hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500",children:"Unselect All"})]})]}),d("div",{className:"overflow-y-auto max-h-64",children:i.length===0?d("div",{className:"px-3 py-2 text-gray-500 text-center",children:"No matches found"}):i.map(c=>{let m=String(c),y=n.has(m);return d("label",{className:"flex items-center px-3 py-2 hover:bg-gray-100 cursor-pointer",children:[d("input",{type:"checkbox",checked:y,onChange:()=>p(m),className:"mr-2 h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"}),d("span",{className:"text-gray-900",children:m})]},m)})})]})]})}function $({data:t,layout:e,config:n}){let _=O(null);return R(()=>{let s=_.current;s&&Plotly.newPlot(s,t,e,n)},[t,e,n]),d("div",{ref:_,className:"w-full"})}function We({columnName:t,columnIdx:e,keys:n,values:_,totals:s,contributions:o}){let{labels:l,hoverText:a}=k(()=>{let r=[],p=[];return n.forEach(c=>{let m=s.get(c)||0,y=o.get(c),b=[];for(let h=0;h<M;h++){if(h===e)continue;let C=y==null?void 0:y.get(h);if(C){let N=Array.from(C.entries()).sort((A,D)=>D[1]-A[1]).slice(0,3).map(([A,D])=>`${A}(${(D/m*100).toFixed(1)}%)`).join("<br>-");N&&b.push(`${P[h]}<br>-`+N)}}let v=`${String(c)} (${m})`;r.push(v),p.push(b.length>0?`${v}<br>${b.join("<br>")}`:v)}),{labels:r,hoverText:p}},[t,e,n,s,o]),f=k(()=>[{type:"sunburst",labels:l,parents:l.map(()=>""),values:_,hovertext:a,hovertemplate:"%{hovertext}<extra></extra>",branchvalues:"total"}],[l,a,_]),i=k(()=>({margin:{l:0,r:0,t:10,b:10},sunburstcolorway:["#4F46E5","#10B981","#F59E0B","#EF4444","#06B6D4","#8B5CF6","#F43F5E"],extendsunburstcolors:!0,height:300}),[]),u=k(()=>({responsive:!0,displayModeBar:!1}),[]);return d("div",{className:"bg-white border border-gray-200 rounded-lg p-4 shadow-sm",children:[d("h3",{className:"text-lg font-semibold mb-3 text-gray-800",children:[t," - Total"]}),l.length?d($,{data:f,layout:i,config:u}):d("div",{className:"text-gray-500 py-6 text-center",children:"No data to display"})]})}function $e({dataset:t}){let e=O(null),n=k(()=>lt(t),[t]);return R(()=>{let _=e.current;if(!_)return;if(!n.ids.length){_.innerHTML=\'<div class="text-gray-500 py-6 text-center">No path data to display</div>\';return}let s={type:"sunburst",ids:n.ids,labels:n.labels,parents:n.parents,values:n.values,branchvalues:"total",maxdepth:3},o={margin:{l:0,r:0,t:10,b:10},sunburstcolorway:["#4F46E5","#10B981","#F59E0B","#EF4444","#06B6D4","#8B5CF6","#F43F5E"],extendsunburstcolors:!0,height:400},l={responsive:!0,displayModeBar:!1};Plotly.newPlot(_,[s],o,l)},[n]),d("div",{ref:e,className:"w-full"})}function lt(t){let e=t,n=new Map,_=new Set;for(let u of e){let r=(u[Ie]+"/"+u[Oe]).trim(),p=Number(u[M])||0;if(!r)continue;let c=r.split("/").filter(y=>y&&y!==".");if(c.length===0)continue;let m=c.join("/");n.set(m,(n.get(m)||0)+p);for(let y=0;y<c.length;y++){let b=c.slice(0,y+1).join("/");_.add(b)}}if(_.size===0)return{ids:[],labels:[],parents:[],values:[]};let s=new Map(Array.from(_,u=>[u,0]));for(let[u,r]of n){let p=u.split("/");for(let c=0;c<p.length;c++){let m=p.slice(0,c+1).join("/");s.set(m,(s.get(m)||0)+r)}}let o=[],l=[],a=[],f=[],i=Array.from(_);i.sort((u,r)=>{let p=u.split("/").length,c=r.split("/").length;return p!==c?p-c:u.localeCompare(r)});for(let u of i){let r=u.split("/"),p=r[r.length-1]||u,c=r.length>1?r.slice(0,-1).join("/"):"";o.push(u),l.push(p),a.push(c),f.push(s.get(u)||0)}return{ids:o,labels:l,parents:a,values:f}}function Ve(){let{initialFilters:t,valueOptions:e}=k(()=>{let l={},a={};for(let f=0;f<M;f++)l[f]=new Set(ce[f].map(i=>String(i))),a[f]=ce[f];return{initialFilters:l,valueOptions:a}},[]),[n,_]=B(t),s=k(()=>T.filter(l=>je(l,n)),[n]),o=k(()=>{let l=new Map,a=P.indexOf("days_bucket"),f=null,i=null;s.forEach(u=>{let r=u[a],p=Number(u[M])||0;l.set(r,(l.get(r)||0)+p),console.error("ADD",r),(!f||r<f)&&(f=r),(!i||r>i)&&(i=r)});for(let u=f;u<i;u++)u%10>0&&u%10<=4&&!l.has(u)&&(l.set(u,0),console.error("ADD",u));return Array.from(l.entries()).sort((u,r)=>u[0]-r[0])},[s]);return d("div",{className:"max-w-4xl mx-auto bg-white rounded-lg shadow-sm p-4",children:[d("h1",{className:"border-gray-300 text-xl",children:"Git Contribution Statistics"}),d("div",{id:"filters",className:"grid grid-cols-[repeat(auto-fit,minmax(220px,1fr))] gap-3",children:W.map((l,a)=>d(Be,{label:P[a],values:e[a],selectedSet:n[a],onChange:f=>_(i=>({...i,[a]:f}))},a))}),d("div",{children:d($,{data:[{x:o.map(l=>l[0]),y:o.map(l=>l[1]),type:"scatter",line:{shape:"spline",smoothing:1.3}}],layout:{xaxis:{visible:!1,type:"category"},yaxis:{visible:!1},height:150,margin:{l:0,r:0,t:0,b:0,pad:0},bargap:0,bargroupgap:0,selectdirection:"h",zoomdirection:"x"},config:{displayModeBar:!1,displaylogo:!1}})}),d("div",{children:[d("h2",{className:"border-b border-gray-300",children:"Column Totals"}),d("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-4 mt-4",children:W.map((l,a)=>{let f=T.filter(c=>je(c,n)),{keys:i,values:u,totals:r,contributions:p}=it(f,a);return d(We,{columnName:P[a],columnIdx:a,keys:i,values:u,totals:r,contributions:p},a)})})]}),d("div",{className:"mt-8",children:[d("h2",{className:"border-b border-gray-300 pb-2.5",children:"Repository Paths Sunburst"}),d("p",{className:"text-sm text-gray-600 mt-2 mb-3",children:["Breakdown by folder structure based on ",d("code",{children:"clusterPath"})," within current filters."]}),d($e,{dataset:s})]})]})}function je(t,e){for(let n=0;n<M;n++){let _=e[n];if(_&&!_.has(String(t[n])))return!1}return!0}function it(t,e){let n=new Map,_=new Map;for(let o of t){let l=o[e],a=Number(o[o.length-1])||0;n.set(l,(n.get(l)||0)+a),_.has(l)||_.set(l,new Map);let f=_.get(l);for(let i=0;i<M;i++){if(i===e)continue;f.has(i)||f.set(i,new Map);let u=f.get(i),r=o[i];u.set(r,(u.get(r)||0)+a)}}let s=Array.from(n.entries()).sort((o,l)=>l[1]-o[1]);return{keys:s.map(([o])=>o),values:s.map(([,o])=>o),totals:n,contributions:_}}var qe=document.getElementById("root");qe&&Se(d(Ve,{}),qe);})();</script>\n</body>\n</html>\nw';
49
+
50
+ // src/output/report_template.ts
51
+ function generateHtmlReport(data, outputFile) {
52
+ const finalOutputPath = path.join(outputFile);
53
+ let htmlContent = dist_default.split("__DATASET_JSON__");
54
+ fs.writeFileSync(finalOutputPath, htmlContent[0]);
55
+ fs.appendFileSync(finalOutputPath, "\n[\n");
56
+ for (let i = 0; i < data.length; i++) {
57
+ fs.appendFileSync(finalOutputPath, JSON.stringify(data[i]) + ",\n");
58
+ }
59
+ fs.appendFileSync(finalOutputPath, "\n]\n");
60
+ fs.appendFileSync(finalOutputPath, htmlContent[1]);
61
+ }
62
+
63
+ // src/git.ts
64
+ var import_path = __toESM(require("path"));
65
+ var import_fs = __toESM(require("fs"));
38
66
 
39
67
  // src/util/exec.ts
40
68
  var import_child_process = require("child_process");
41
69
  function execAsync(command, args = [], options = {}) {
42
70
  return new Promise((resolve2, reject) => {
43
- const child = (0, import_child_process.spawn)(command, args, { ...options, shell: true });
71
+ const child = (0, import_child_process.spawn)(command, args, { ...options });
44
72
  let stdout = [];
45
- let stderr = [];
73
+ let stderr2 = [];
46
74
  let stdoutBuffer = "";
47
75
  let stderrBuffer = "";
48
76
  child.stdout.on("data", (data) => {
@@ -57,7 +85,7 @@ function execAsync(command, args = [], options = {}) {
57
85
  stderrBuffer += data.toString();
58
86
  const lines = stderrBuffer.split("\n");
59
87
  for (let i = 0; i < lines.length - 1; i++) {
60
- stderr.push(lines[i]);
88
+ stderr2.push(lines[i]);
61
89
  }
62
90
  stderrBuffer = lines[lines.length - 1];
63
91
  });
@@ -69,282 +97,19 @@ function execAsync(command, args = [], options = {}) {
69
97
  stdout.push(stdoutBuffer);
70
98
  }
71
99
  if (stderrBuffer.length > 0) {
72
- stderr.push(stderrBuffer);
100
+ stderr2.push(stderrBuffer);
73
101
  }
74
102
  if (code === 0) {
75
- resolve2({ stdout, stderr });
103
+ resolve2({ stdout, stderr: stderr2 });
76
104
  } else {
77
- reject(new Error(`Command failed with code ${code}
78
- ${stderr.join("\n")}`));
105
+ reject(new Error(`Command ${command} ${JSON.stringify(args)} failed with code ${code}
106
+ ${stderr2.join("\n")}`));
79
107
  }
80
108
  });
81
109
  });
82
110
  }
83
111
 
84
- // src/index.ts
85
- var fs4 = __toESM(require("fs"));
86
- var path4 = __toESM(require("path"));
87
-
88
- // src/output/report_template.ts
89
- var path = __toESM(require("path"));
90
- var fs = __toESM(require("fs"));
91
-
92
- // src/output/report_template.html
93
- var report_template_default = `<!DOCTYPE html>
94
- <!--suppress TypeScriptMissingConfigOption -->
95
- <html lang="en">
96
- <head>
97
- <meta charset="UTF-8">
98
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
99
- <title>Git Blame Statistics</title>
100
- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
101
- <script src="https://cdn.plot.ly/plotly-3.3.0.min.js" charset="utf-8"></script>
102
- <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
103
- <!-- React 18 UMD builds -->
104
- <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
105
- <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
106
- <!-- Babel Standalone for in-browser JSX transform -->
107
- <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
108
- </head>
109
- <body class="font-sans m-0 bg-gray-50 text-gray-900">
110
- <div id="root"></div>
111
- <script type="text/babel" data-presets="react">
112
- const RAW_DATASET =
113
- __DATASET_JSON__
114
- || [];
115
- // Fixed schema as per pipeline: [author, days_bucket, lang, clusterPath, repoName, count]
116
- const HEADER_LABELS = ["author", "days_bucket", "lang", "clusterPath", "repoName", "count"];
117
- // -------- Client-side grouping/filtering engine --------
118
- const KEY_INDEX = Object.fromEntries(HEADER_LABELS.map((k, i) => [k, i]));
119
- const COUNT_IDX_FROM_END = 1; // last element is count
120
-
121
- const allKeys = HEADER_LABELS.filter(k => k !== 'count');
122
- const TOP_N = 20;
123
- const BUCKET_COLORS = [
124
- 'rgba(214, 40, 40, 0.7)',
125
- 'rgba(247, 127, 0, 0.7)',
126
- 'rgba(252, 191, 73, 0.7)',
127
- 'rgba(168, 218, 142, 0.7)',
128
- 'rgba(75, 192, 192, 0.7)',
129
- 'rgba(54, 162, 235, 0.7)',
130
- 'rgba(153, 102, 255, 0.7)',
131
- 'rgba(201, 203, 207, 0.7)'
132
- ];
133
-
134
- function uniqueValues(arr, idx) {
135
- const set = new Set(arr.map(r => r[idx]));
136
- return Array.from(set);
137
- }
138
-
139
- function pivot(
140
- dataset,
141
- primaryKeyIndex,
142
- secondaryKeyIndex,
143
- filterState
144
- ) {
145
- const pivot = new Map();
146
- const totals = new Map();
147
- const secValuesSet = new Set();
148
- for (const row of dataset) {
149
- let ok = true;
150
- for (const key of allKeys) {
151
- const idx = KEY_INDEX[key];
152
- const sel = filterState[key];
153
- if (sel && !sel.has(String(row[idx]))) {
154
- ok = false;
155
- break;
156
- }
157
- }
158
- if (!ok) continue;
159
- const pk = row[primaryKeyIndex];
160
- const sk = row[secondaryKeyIndex];
161
- const count = Number(row[row.length - COUNT_IDX_FROM_END]) || 0;
162
- if (!pivot.has(pk)) pivot.set(pk, new Map());
163
- pivot.get(pk).set(sk, (pivot.get(pk).get(sk) || 0) + count);
164
- totals.set(pk, (totals.get(pk) || 0) + count);
165
- secValuesSet.add(sk);
166
- }
167
- const primaryKeys = Array.from(pivot.keys()).sort((a, b) => (totals.get(b) || 0) - (totals.get(a) || 0));
168
- const secondaryKeys = Array.from(secValuesSet).sort((a, b) => String(a).localeCompare(String(b)));
169
- return {pivot, totals, primaryKeys, secondaryKeys};
170
- }
171
-
172
- // ---------- React Components ----------
173
- function ChartView({labels, datasets}) {
174
- const canvasRef = React.useRef(null);
175
- const chartRef = React.useRef(null);
176
- React.useEffect(() => {
177
- const ctx = canvasRef.current.getContext('2d');
178
- chartRef.current = new Chart(ctx, {
179
- type: 'bar',
180
- data: {labels: [], datasets: []},
181
- options: {
182
- indexAxis: 'y',
183
- scales: {
184
- x: {stacked: true, beginAtZero: true},
185
- y: {stacked: true, ticks: {autoSkip: false}}
186
- },
187
- plugins: {
188
- tooltip: {
189
- callbacks: {
190
- label: function (context) {
191
- let label = context.dataset.label || '';
192
- if (label) label += ': ';
193
- if (context.parsed.x !== null) label += context.parsed.x.toLocaleString();
194
- return label;
195
- }
196
- }
197
- }
198
- }
199
- }
200
- });
201
- return () => {
202
- chartRef.current && chartRef.current.destroy();
203
- };
204
- }, []);
205
-
206
- React.useEffect(() => {
207
- if (!chartRef.current) return;
208
- chartRef.current.data.labels = labels;
209
- chartRef.current.data.datasets = datasets;
210
- chartRef.current.update();
211
- }, [labels, datasets]);
212
-
213
- return <canvas ref={canvasRef}/>;
214
- }
215
-
216
- function MultiSelect({label, values, selectedSet, onChange}) {
217
- const selCount = selectedSet.size;
218
- const total = values.length;
219
- return (
220
- <div>
221
- <label className="block font-semibold mb-1.5">
222
- {label}{' '}
223
- <span className="text-gray-600 font-normal">({selCount}/{total})</span>
224
- </label>
225
- <select
226
- multiple
227
- size={Math.min(8, Math.max(3, values.length))}
228
- className="w-full"
229
- onChange={(e) => {
230
- const chosen = Array.from(e.target.selectedOptions).map(o => o.value);
231
- onChange(new Set(chosen));
232
- }}
233
- >
234
- {values.map(v => {
235
- const strVal = String(v);
236
- return (
237
- <option key={strVal} value={strVal} selected={selectedSet.has(strVal)}>
238
- {strVal}
239
- </option>
240
- );
241
- })}
242
- </select>
243
- </div>
244
- );
245
- }
246
-
247
- function App() {
248
- const {initialFilters, valueOptions} = React.useMemo(() => {
249
- const filters = {};
250
- const options = {};
251
- for (const key of allKeys) {
252
- const idx = KEY_INDEX[key];
253
- const values = uniqueValues(RAW_DATASET, idx).sort((a, b) => {
254
- if (key === 'days_bucket') return Number(a) - Number(b);
255
- return String(a).localeCompare(String(b));
256
- });
257
- filters[key] = new Set(values.map(v => String(v)));
258
- options[key] = values;
259
- }
260
- return {initialFilters: filters, valueOptions: options};
261
- }, []);
262
- const [filters, setFilters] = React.useState(initialFilters);
263
- const [primaryKeyIndex, setPrimaryKeyIndex] = React.useState(0);
264
- const [secondaryKeyIndex, setSecondaryKeyIndex] = React.useState(1);
265
-
266
- const {chartLabels, datasets} = React.useMemo(() => {
267
- const {
268
- pivot: pv,
269
- primaryKeys,
270
- secondaryKeys
271
- } = pivot(RAW_DATASET, primaryKeyIndex, secondaryKeyIndex, filters);
272
- const chartPrimaryKeys = primaryKeys.slice(0, TOP_N);
273
- const ds = secondaryKeys.map((sk, i) => ({
274
- label: String(sk),
275
- data: chartPrimaryKeys.map(pk => (pv.get(pk)?.get(sk)) || 0),
276
- backgroundColor: BUCKET_COLORS[i % BUCKET_COLORS.length]
277
- }));
278
- return {chartLabels: chartPrimaryKeys, datasets: ds};
279
- }, [filters, primaryKeyIndex, secondaryKeyIndex]);
280
-
281
- return (
282
- <div className="max-w-4xl mx-auto my-5 p-5 bg-white rounded-lg shadow-sm">
283
- <h1 className="border-b border-gray-300 pb-2.5">Git Contribution Statistics</h1>
284
- <div className="controls">
285
- <h2 className="border-b border-gray-300 pb-2.5">Controls</h2>
286
- <div className="flex gap-4 flex-wrap items-center">
287
- <label>
288
- Primary group:
289
- <select value={primaryKeyIndex} onChange={e => setPrimaryKeyIndex(Number(e.target.value))}>
290
- {allKeys.map((__, i) => (
291
- <option key={i} value={i}>{HEADER_LABELS[i]}</option>
292
- ))}
293
- </select>
294
- </label>
295
- <label>
296
- Secondary group:
297
- <select value={secondaryKeyIndex}
298
- onChange={e => setSecondaryKeyIndex(Number(e.target.value))}>
299
- {allKeys.map((__, i) => (
300
- <option key={i} value={i}>{HEADER_LABELS[i]}</option>
301
- ))}
302
- </select>
303
- </label>
304
- </div>
305
- <div id="filters" className="mt-3 grid grid-cols-[repeat(auto-fit,minmax(220px,1fr))] gap-3">
306
- {allKeys.map((key, idx) => (
307
- <MultiSelect
308
- key={idx}
309
- label={HEADER_LABELS[idx]}
310
- values={valueOptions[key]}
311
- selectedSet={filters[key]}
312
- onChange={(newSet) => setFilters(prev => ({...prev, [key]: newSet}))}
313
- />
314
- ))}
315
- </div>
316
- </div>
317
- <div className="mt-5">
318
- <h2 id="chartTitle" className="border-b border-gray-300 pb-2.5">Chart</h2>
319
- <ChartView labels={chartLabels} datasets={datasets}/>
320
- </div>
321
- </div>
322
- );
323
- }
324
-
325
- const root = ReactDOM.createRoot(document.getElementById('root'));
326
- root.render(<App/>);
327
- </script>
328
- </body>
329
- </html>
330
- `;
331
-
332
- // src/output/report_template.ts
333
- function generateHtmlReport(data, outputFile) {
334
- const finalOutputPath = path.join(outputFile);
335
- let htmlContent = report_template_default.split("__DATASET_JSON__");
336
- fs.writeFileSync(finalOutputPath, htmlContent[0]);
337
- fs.appendFileSync(finalOutputPath, "\n[\n");
338
- for (let i = 0; i < data.length; i++) {
339
- fs.appendFileSync(finalOutputPath, JSON.stringify(data[i]) + ",\n");
340
- }
341
- fs.appendFileSync(finalOutputPath, "\n]\n");
342
- fs.appendFileSync(finalOutputPath, htmlContent[1]);
343
- }
344
-
345
112
  // src/git.ts
346
- var import_path = __toESM(require("path"));
347
- var import_fs = __toESM(require("fs"));
348
113
  async function executeGitBlamePorcelain(file, repoRoot, revisionBoundary, since) {
349
114
  const args = ["blame", "--line-porcelain"];
350
115
  if (since) {
@@ -366,18 +131,18 @@ function parsePorcelain(blameOutput, fields) {
366
131
  const commiterTimePos = fields.indexOf("committer-time");
367
132
  const boundaryPos = fields.indexOf("boundary");
368
133
  const commitPos = fields.indexOf("commit");
369
- let emptyRow = [...fields];
134
+ let emptyRow = {};
370
135
  if (commiterTimePos >= 0) {
371
136
  emptyRow[commiterTimePos] = 0;
372
137
  }
373
138
  if (boundaryPos >= 0) {
374
139
  emptyRow[boundaryPos] = 0;
375
140
  }
376
- let nextRow = [...emptyRow];
141
+ let nextRow = { ...emptyRow };
377
142
  const result = [];
378
143
  for (const line of blameOutput) {
379
144
  if (line.startsWith(" ")) {
380
- result.push([...nextRow]);
145
+ result.push({ ...nextRow });
381
146
  continue;
382
147
  }
383
148
  if (commitPos >= 0) {
@@ -385,22 +150,22 @@ function parsePorcelain(blameOutput, fields) {
385
150
  if (firstSpace === 40) {
386
151
  const possibleHash = line.substring(0, firstSpace);
387
152
  if (/^\^?[0-9a-f]{40}$/i.test(possibleHash)) {
388
- nextRow = [...emptyRow];
389
- nextRow[commitPos] = possibleHash.replace(/^\^/, "");
153
+ nextRow = { ...emptyRow };
154
+ nextRow.commit = possibleHash.replace(/^\^/, "");
390
155
  continue;
391
156
  }
392
157
  }
393
158
  }
394
159
  if (userPos >= 0 && line.startsWith("author ")) {
395
- nextRow[userPos] = line.substring("author ".length).replace(/^<|>$/g, "");
160
+ nextRow.author = line.substring("author ".length).replace(/^<|>$/g, "");
396
161
  continue;
397
162
  }
398
163
  if (commiterTimePos >= 0 && line.startsWith("committer-time ")) {
399
- nextRow[commiterTimePos] = parseInt(line.substring("committer-time ".length), 10);
164
+ nextRow.time = parseInt(line.substring("committer-time ".length), 10);
400
165
  continue;
401
166
  }
402
167
  if (boundaryPos >= 0 && line.startsWith("boundary")) {
403
- nextRow[boundaryPos] = 1;
168
+ nextRow.boundary = 1;
404
169
  }
405
170
  }
406
171
  return result;
@@ -425,37 +190,14 @@ async function findRevision(repoRoot, commitsBack) {
425
190
  }
426
191
  return void 0;
427
192
  }
428
-
429
- // src/vfs.ts
430
- var import_fs2 = __toESM(require("fs"));
431
- var import_path2 = __toESM(require("path"));
432
- var RealFileSystemImpl = class {
433
- constructor(basePath) {
434
- this.basePath = import_path2.default.normalize(basePath);
435
- }
436
- async read(filePath) {
437
- return import_fs2.default.readFileSync(this.resolve(filePath), "utf8");
438
- }
439
- async write(filePath, content) {
440
- let path1 = import_path2.default.resolve(this.resolve(filePath), "..");
441
- import_fs2.default.mkdirSync(path1, { recursive: true });
442
- import_fs2.default.writeFileSync(this.resolve(filePath), content);
443
- }
444
- async append(filePath, content) {
445
- return new Promise((resolve2, reject) => {
446
- import_fs2.default.appendFile(this.resolve(filePath), content, (err) => {
447
- if (!err) {
448
- resolve2(void 0);
449
- } else {
450
- reject(err);
451
- }
452
- });
453
- });
454
- }
455
- resolve(filePath) {
456
- return import_path2.default.normalize(`${this.basePath}/${filePath}`);
457
- }
458
- };
193
+ async function git_ls_files(repoRootPath, subdirPath) {
194
+ const { stdout: lsFilesOut } = await execAsync(
195
+ "git",
196
+ ["ls-files", "--", subdirPath || "."],
197
+ { cwd: repoRootPath }
198
+ );
199
+ return lsFilesOut.filter((line) => line && line.length > 0);
200
+ }
459
201
 
460
202
  // src/util/AsyncGeneratorUtil.ts
461
203
  var AsyncGeneratorUtil = class {
@@ -503,22 +245,38 @@ var AsyncGeneratorUtil = class {
503
245
  });
504
246
  }
505
247
  };
506
- var AsyncIteratorWrapperImpl = class _AsyncIteratorWrapperImpl {
248
+ var AsyncIteratorWrapperImpl = class {
507
249
  constructor(source) {
508
250
  this.source = source;
509
251
  }
252
+ chunked(size) {
253
+ return streamOf(this.__chunked(size));
254
+ }
255
+ async *__chunked(size) {
256
+ let chunk = [];
257
+ for await (const item of this.source) {
258
+ chunk.push(item);
259
+ if (chunk.length === size) {
260
+ yield chunk;
261
+ chunk = [];
262
+ }
263
+ }
264
+ if (chunk.length > 0) {
265
+ yield chunk;
266
+ }
267
+ }
510
268
  get() {
511
269
  return this.source;
512
270
  }
513
271
  map(mapper) {
514
- return new _AsyncIteratorWrapperImpl(async function* (source, mapper2) {
272
+ return streamOf(async function* (source, mapper2) {
515
273
  for await (const item of source) {
516
274
  yield mapper2(item);
517
275
  }
518
276
  }(this.source, mapper));
519
277
  }
520
278
  flatMap(mapper) {
521
- return new _AsyncIteratorWrapperImpl(AsyncGeneratorUtil.flatMap(this.source, mapper));
279
+ return streamOf(AsyncGeneratorUtil.flatMap(this.source, mapper));
522
280
  }
523
281
  async forEach(consumer) {
524
282
  for await (const item of this.source) {
@@ -526,217 +284,401 @@ var AsyncIteratorWrapperImpl = class _AsyncIteratorWrapperImpl {
526
284
  }
527
285
  }
528
286
  };
287
+ function streamOf(source) {
288
+ return new AsyncIteratorWrapperImpl(source);
289
+ }
290
+ var stream;
291
+ ((stream2) => {
292
+ function ofArrayPromise(p) {
293
+ async function* __ofArrayPromise(p2) {
294
+ const items = await p2;
295
+ for (const item of items) {
296
+ yield item;
297
+ }
298
+ }
299
+ return streamOf(__ofArrayPromise(p));
300
+ }
301
+ stream2.ofArrayPromise = ofArrayPromise;
302
+ })(stream || (stream = {}));
529
303
 
530
304
  // src/util/file_tree_clustering.ts
531
- function clusterFiles(files, clusterMaxSize, clusterMinSize) {
532
- let fileInfos = files.map((it) => {
533
- let arr = it.split("/");
534
- return { arr, str: it };
535
- });
536
- let initialCluster = { path: [], files: fileInfos, isLeftovers: false, isUnclusterable: false };
537
- let clusterGroups = [initialCluster];
538
- let changes = true;
539
- function mostFrequent(arr) {
540
- const counts = {};
541
- for (const item of arr) {
542
- counts[item] = (counts[item] || 0) + 1;
305
+ var graph;
306
+ ((graph2) => {
307
+ function buildGraph(files) {
308
+ const result = {
309
+ path: [],
310
+ children: {},
311
+ value: [],
312
+ size: 0
313
+ };
314
+ files.forEach((file) => {
315
+ let targetDir = result;
316
+ file.arr.slice(0, -1).forEach((pathSegment) => {
317
+ targetDir.size++;
318
+ if (!Object.prototype.hasOwnProperty.call(targetDir.children, pathSegment)) {
319
+ targetDir.children[pathSegment] = {
320
+ path: targetDir.path.concat([pathSegment]),
321
+ children: {},
322
+ value: [],
323
+ size: 0
324
+ };
325
+ }
326
+ targetDir = targetDir.children[pathSegment];
327
+ });
328
+ targetDir.value.push(file);
329
+ });
330
+ return result;
331
+ }
332
+ graph2.buildGraph = buildGraph;
333
+ function flatten(graphNode) {
334
+ if (Object.keys(graphNode.children).length === 0) {
335
+ return graphNode.value;
543
336
  }
544
- let maxCount = 0;
545
- let mostFrequentItem = arr[0];
546
- for (const item in counts) {
547
- if (counts[item] > maxCount) {
548
- maxCount = counts[item];
549
- mostFrequentItem = item;
550
- }
337
+ let fileInfos = Object.values(graphNode.children).flatMap((child) => flatten(child));
338
+ return fileInfos.concat(graphNode.value);
339
+ }
340
+ function bubbleMicroLeftovers(graphNode, clusterMaxSize, clusterMinSize) {
341
+ if (Object.keys(graphNode.children).length === 0) {
342
+ return false;
551
343
  }
552
- return mostFrequentItem;
553
- }
554
- while (changes) {
555
- changes = false;
556
- clusterGroups = clusterGroups.flatMap((originalCluster) => {
557
- if (originalCluster.files.length > clusterMaxSize && !originalCluster.isUnclusterable) {
558
- let l = originalCluster.path.length;
559
- let mf = mostFrequent(originalCluster.files.map((it) => it.arr[l] || "$$$notfound$$$"));
560
- let newClusterFiles = [];
561
- let remainingFiles = originalCluster.files.filter((it) => {
562
- let nextPathSegment = it.arr[l];
563
- if (nextPathSegment === mf) {
564
- newClusterFiles.push(it);
565
- return false;
566
- } else {
567
- return true;
568
- }
569
- });
570
- changes = true;
571
- if (remainingFiles.length === 0) {
572
- return [
573
- {
574
- path: originalCluster.path.concat([mf]),
575
- files: newClusterFiles,
576
- isLeftovers: false
577
- }
578
- ];
579
- }
580
- if (newClusterFiles.length < clusterMinSize || remainingFiles.length < clusterMinSize) {
581
- return [{ ...originalCluster, isUnclusterable: true }];
344
+ if (graphNode.size < clusterMaxSize) {
345
+ let allFiles = flatten(graphNode);
346
+ graphNode.children = {};
347
+ graphNode.value = allFiles;
348
+ graphNode.size = allFiles.length;
349
+ return true;
350
+ }
351
+ let result = false;
352
+ Object.entries(graphNode.children).forEach(([k, child]) => {
353
+ if (child.value.length < clusterMinSize) {
354
+ graphNode.value.push(...child.value);
355
+ graphNode.size += child.value.length;
356
+ child.size -= child.value.length;
357
+ child.value = [];
358
+ if (child.size === 0) {
359
+ delete graphNode.children[k];
360
+ result = true;
582
361
  }
583
- return [
584
- {
585
- path: originalCluster.path.concat([mf]),
586
- files: newClusterFiles,
587
- isLeftovers: false
588
- },
589
- {
590
- path: originalCluster.path,
591
- files: remainingFiles,
592
- isLeftovers: true
593
- }
594
- ];
595
- } else {
596
- return [originalCluster];
597
362
  }
598
363
  });
364
+ return result;
599
365
  }
600
- return clusterGroups.map((cluster) => {
366
+ graph2.bubbleMicroLeftovers = bubbleMicroLeftovers;
367
+ function unpackSmallest(graphNode, clusterMaxSize, clusterMinSize) {
368
+ let childrenSortedAsc = Object.entries(graphNode.children).sort((a, b) => a[1].size - b[1].size);
369
+ if (childrenSortedAsc.length === 0) {
370
+ return false;
371
+ }
372
+ let [k, child] = childrenSortedAsc[0];
373
+ let canFit = child.size + graphNode.value.length <= clusterMaxSize;
374
+ let cannotIsolate = graphNode.value.length < clusterMinSize;
375
+ if (canFit && cannotIsolate) {
376
+ graphNode.value.push(...flatten(child));
377
+ graphNode.size++;
378
+ delete graphNode.children[k];
379
+ return true;
380
+ }
381
+ return false;
382
+ }
383
+ function bubbleMicroLeftoversRecursive(graphNode, clusterMaxSize, clusterMinSize) {
384
+ let changed = Object.values(graphNode.children).map((child) => {
385
+ bubbleMicroLeftoversRecursive(child, clusterMaxSize, clusterMinSize);
386
+ }).find(Boolean) || false;
387
+ changed = unpackSmallest(graphNode, clusterMaxSize, clusterMinSize) || changed;
388
+ changed = bubbleMicroLeftovers(graphNode, clusterMaxSize, clusterMinSize) || changed;
389
+ return changed;
390
+ }
391
+ graph2.bubbleMicroLeftoversRecursive = bubbleMicroLeftoversRecursive;
392
+ function collect(graphNode) {
393
+ let result = [];
394
+ function collectRecursive(node) {
395
+ result.push(node);
396
+ Object.values(node.children).forEach((child) => {
397
+ collectRecursive(child);
398
+ });
399
+ }
400
+ collectRecursive(graphNode);
401
+ return result;
402
+ }
403
+ graph2.collect = collect;
404
+ })(graph || (graph = {}));
405
+ function clusterFiles(files, clusterMaxSize, clusterMinSize) {
406
+ let fileInfos = files.sort().map((it) => {
407
+ let arr = it.split("/");
408
+ return { arr, str: it };
409
+ });
410
+ let graphNode = graph.buildGraph(fileInfos);
411
+ while (graph.bubbleMicroLeftoversRecursive(graphNode, clusterMaxSize, clusterMinSize)) {
412
+ }
413
+ let subclusters = graph.collect(graphNode).filter((it) => it.value.length > 0).sort((a, b) => b.path.join("/").localeCompare(a.path.join("/"))).map((it) => ({
414
+ path: it.path,
415
+ files: it.value,
416
+ isLeftovers: it.size > clusterMinSize,
417
+ isUnclusterable: it.size <= clusterMinSize
418
+ }));
419
+ return subclusters.map((cluster) => {
601
420
  let files2 = cluster.files;
602
- let path5 = cluster.path;
421
+ let path6 = cluster.path;
603
422
  let files1 = files2.map((it) => it.str);
604
423
  return {
605
- path: path5.join("/"),
606
- files: files1,
424
+ path: path6.join("/"),
425
+ files: files1.sort(),
607
426
  weight: files1.length,
608
427
  isLeftovers: cluster.isLeftovers
609
428
  };
610
429
  });
611
430
  }
612
431
 
613
- // src/index.ts
614
- var sigintCaught = false;
615
- function getDirectories(source) {
616
- if (!fs4.existsSync(source) || !fs4.statSync(source).isDirectory()) return [];
432
+ // src/util/dataset.ts
433
+ async function* distinctCount(source) {
434
+ const map = /* @__PURE__ */ new Map();
435
+ for await (const row of source) {
436
+ const key = JSON.stringify(row);
437
+ let count = row?.count ?? 1;
438
+ if (map.has(key)) {
439
+ map.get(key).count += count;
440
+ } else {
441
+ map.set(key, { row, count });
442
+ }
443
+ }
444
+ for (const { row, count } of map.values()) {
445
+ yield [row, count];
446
+ }
447
+ }
448
+
449
+ // src/discovery.ts
450
+ var import_fs2 = __toESM(require("fs"));
451
+ var import_path2 = __toESM(require("path"));
452
+
453
+ // src/util/util.ts
454
+ var util;
455
+ ((util2) => {
456
+ function distinct(arr) {
457
+ return [...new Set(arr)];
458
+ }
459
+ util2.distinct = distinct;
460
+ function daysAgo(epoch) {
461
+ const now = Date.now();
462
+ const diff = now - epoch * 1e3;
463
+ return Math.floor(diff / (1e3 * 60 * 60 * 24));
464
+ }
465
+ util2.daysAgo = daysAgo;
466
+ function bucket(n, buckets) {
467
+ for (let i = 1; i < buckets.length; i++) {
468
+ if (n > buckets[i - 1] && n < buckets[i]) return buckets[i - 1];
469
+ }
470
+ return -1;
471
+ }
472
+ util2.bucket = bucket;
473
+ function yyyyMM(epoch) {
474
+ const date = new Date(epoch * 1e3);
475
+ let yyyyStr = date.getFullYear().toString();
476
+ let MMStr = (date.getMonth() + 1 / 4).toString().padStart(1, "0");
477
+ return parseInt(yyyyStr) * 10 + parseInt(MMStr);
478
+ }
479
+ util2.yyyyMM = yyyyMM;
480
+ })(util || (util = {}));
481
+
482
+ // src/discovery.ts
483
+ function getDirectories(absoluteDirPath) {
484
+ if (!import_fs2.default.existsSync(absoluteDirPath) || !import_fs2.default.statSync(absoluteDirPath).isDirectory()) return [];
617
485
  const ignoredDirs = /* @__PURE__ */ new Set([".git", "node_modules"]);
618
486
  try {
619
- return fs4.readdirSync(source, { withFileTypes: true }).filter((dirent) => dirent.isDirectory() && !ignoredDirs.has(dirent.name)).map((dirent) => path4.join(source, dirent.name));
487
+ return import_fs2.default.readdirSync(absoluteDirPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory() && !ignoredDirs.has(dirent.name)).map((dirent) => import_path2.default.join(absoluteDirPath, dirent.name));
620
488
  } catch (error) {
621
- console.error(`Could not read directory: ${source}`);
489
+ console.error(`Could not read directory: ${absoluteDirPath}`);
622
490
  return [];
623
491
  }
624
492
  }
625
- async function* forEachRepoFile(repoPath, doProcessFile) {
626
- console.error(`
627
- Processing repository: ${repoPath || "."}`);
628
- const originalCwd = process.cwd();
629
- const discoveryPath = path4.resolve(originalCwd, repoPath);
630
- if (!fs4.existsSync(discoveryPath)) {
631
- console.error(`Error: Path does not exist: ${discoveryPath}. Skipping.`);
632
- return;
493
+ function findRepositories(absolutePath, depth) {
494
+ if (depth <= 0) return [];
495
+ if (!import_path2.default.isAbsolute(absolutePath)) throw new Error(`Path must be absolute: ${absolutePath}`);
496
+ if (!import_fs2.default.existsSync(absolutePath)) throw new Error(`Path does not exist: ${absolutePath}`);
497
+ if (!import_fs2.default.statSync(absolutePath).isDirectory()) throw new Error(`Path is not a directory: ${absolutePath}`);
498
+ if (isGitRepo(absolutePath)) return [absolutePath];
499
+ let result = getDirectories(absolutePath).flatMap((dir) => findRepositories(dir, depth - 1));
500
+ return util.distinct(result).sort();
501
+ }
502
+ function getRepoPathsToProcess(inputPaths) {
503
+ let repos = inputPaths.map((it) => import_path2.default.resolve(it)).flatMap((it) => findRepositories(it, 3));
504
+ return util.distinct(repos).sort();
505
+ }
506
+
507
+ // src/progress.ts
508
+ var process2 = __toESM(require("node:process"));
509
+ var Progress = class {
510
+ constructor() {
511
+ this.progress = {};
512
+ this.messages = {};
513
+ this.startTime = {};
514
+ this.currentInterval = void 0;
515
+ }
516
+ setProgress(name, current, max = void 0) {
517
+ this.progress[name] = [current, max ?? this.progress[name]?.[1] ?? void 0];
518
+ if (!this.startTime[name]) this.startTime[name] = Date.now();
519
+ }
520
+ setMessage(name, message) {
521
+ this.messages[name] = message;
522
+ }
523
+ stop(name) {
524
+ delete this.progress[name];
525
+ delete this.messages[name];
526
+ delete this.startTime[name];
527
+ }
528
+ destroy() {
529
+ Object.keys(this.progress).forEach((key) => this.stop(key));
530
+ clearInterval(this.currentInterval);
531
+ }
532
+ showProgress(period) {
533
+ this.currentInterval = setInterval(() => {
534
+ const now = Date.now();
535
+ const progress2 = Object.entries(this.progress).map(([key, [value, max]]) => {
536
+ let eta = "?";
537
+ const startTime = this.startTime[key];
538
+ if (startTime && max !== void 0) {
539
+ const elapsed = (now - startTime) / 1e3;
540
+ const rate = value / elapsed;
541
+ const remaining = max - value;
542
+ const etaSeconds = Math.round(remaining / rate);
543
+ const days = Math.floor(etaSeconds / 86400);
544
+ const hours = Math.floor(etaSeconds % 86400 / 3600);
545
+ const minutes = Math.floor(etaSeconds % 3600 / 60);
546
+ const seconds = etaSeconds % 60;
547
+ const parts = [];
548
+ if (days > 0) parts.push(`${days}d`);
549
+ if (hours > 0) parts.push(`${hours}h`);
550
+ if (minutes > 0) parts.push(`${minutes}m`);
551
+ if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`);
552
+ eta = ` ETA: ${parts.join("")}`;
553
+ }
554
+ return `${key}: [${value}/${max ?? "?"}]${eta} ${this.messages[key] ?? ""}`;
555
+ }).join(", ");
556
+ if (progress2.length === 0) return;
557
+ process2.stderr.clearLine(0, () => {
558
+ process2.stderr.write("\r" + progress2);
559
+ });
560
+ }, period);
633
561
  }
634
- const gitCommandPath = fs4.statSync(discoveryPath).isDirectory() ? discoveryPath : path4.dirname(discoveryPath);
635
- let repoRoot;
636
- try {
637
- repoRoot = (0, import_child_process2.execSync)("git rev-parse --show-toplevel", { cwd: gitCommandPath, stdio: "pipe" }).toString().trim();
638
- } catch (e) {
639
- console.error(`Error: Could not find git repository at ${gitCommandPath}. Skipping.`);
640
- return;
562
+ };
563
+
564
+ // src/vfs.ts
565
+ var import_fs3 = __toESM(require("fs"));
566
+ var import_path3 = __toESM(require("path"));
567
+ var RealFileSystemImpl = class {
568
+ constructor(basePath) {
569
+ this.basePath = import_path3.default.normalize(basePath);
641
570
  }
642
- const repoName = path4.basename(repoRoot);
643
- let revisionBoundary = await findRevision(repoRoot, 1e3);
644
- const finalTargetPath = path4.relative(repoRoot, discoveryPath);
645
- const { stdout: lsFilesOut } = await execAsync(
646
- "git",
647
- ["ls-files", "--", finalTargetPath || "."],
648
- { cwd: repoRoot }
649
- );
650
- const files = lsFilesOut.filter((line) => line && line.length > 0);
651
- let minClusterSize = Math.floor(Math.max(5, files.length / 1e3));
652
- let maxClusterSize = Math.round(Math.max(20, minClusterSize * 2));
571
+ async read(filePath) {
572
+ return import_fs3.default.readFileSync(this.resolve(filePath), "utf8");
573
+ }
574
+ async write(filePath, content) {
575
+ let path1 = import_path3.default.resolve(this.resolve(filePath), "..");
576
+ import_fs3.default.mkdirSync(path1, { recursive: true });
577
+ import_fs3.default.writeFileSync(this.resolve(filePath), content);
578
+ }
579
+ async append(filePath, content) {
580
+ return new Promise((resolve2, reject) => {
581
+ import_fs3.default.appendFile(this.resolve(filePath), content, (err) => {
582
+ if (!err) {
583
+ resolve2(void 0);
584
+ } else {
585
+ reject(err);
586
+ }
587
+ });
588
+ });
589
+ }
590
+ resolve(filePath) {
591
+ return import_path3.default.normalize(`${this.basePath}/${filePath}`);
592
+ }
593
+ };
594
+
595
+ // src/index.ts
596
+ var sigintCaught = false;
597
+ var progress = null;
598
+ var dataDir = new RealFileSystemImpl("./.git-stats");
599
+ async function* getRepositoryFiles(repoRelativePath) {
600
+ console.error(`
601
+ Processing repository: ${repoRelativePath || "."}`);
602
+ const absoluteRepoPath = path5.resolve(process.cwd(), repoRelativePath);
603
+ const repoName = path5.basename(absoluteRepoPath);
604
+ let revisionBoundary = await findRevision(absoluteRepoPath, 1e3);
605
+ const files = await git_ls_files(absoluteRepoPath, ".");
606
+ let minClusterSize = Math.floor(Math.max(2, files.length / 100));
607
+ let maxClusterSize = Math.round(Math.max(20, files.length / 30));
608
+ console.error(`Found ${files.length} files to analyze in '${repoName}'...`);
653
609
  console.error(`Clustering ${files.length} into ${minClusterSize}..${maxClusterSize}+ sized chunks`);
654
610
  const filesClustered = clusterFiles(
655
611
  files,
656
612
  maxClusterSize,
657
613
  minClusterSize
658
614
  );
659
- console.error(filesClustered.map((it) => `${it.path}${it.isLeftovers ? "/*" : ""} (${it.weight})`));
660
- let clusterPaths = filesClustered.map((it) => it.path);
661
- console.error(`Found ${files.length} files to analyze in '${repoName}'...`);
662
- let filesShuffled = [...files].sort(() => Math.random() - 0.5);
663
- for (let i = 0; i < files.length; i++) {
615
+ console.error(filesClustered.map((it) => `${it.path} (${it.weight})`));
616
+ let filesShuffled = filesClustered.flatMap(
617
+ (cluster) => cluster.files.flatMap((file) => ({
618
+ file,
619
+ cluster: cluster.path
620
+ }))
621
+ );
622
+ for (let i = filesShuffled.length - 1; i > 0; i--) {
623
+ const j = Math.floor(Math.random() * (i + 1));
624
+ [filesShuffled[i], filesShuffled[j]] = [filesShuffled[j], filesShuffled[i]];
625
+ }
626
+ for (let i = 0; i < filesShuffled.length; i++) {
664
627
  if (sigintCaught) break;
665
- const file = filesShuffled[i];
666
- const progressMessage = `[${i + 1}/${files.length}] Analyzing: ${file}`;
667
- process.stderr.write(progressMessage.padEnd(process.stderr.columns || 80, " ") + "\r");
668
- try {
669
- let clusterPath = clusterPaths.find((it) => file.startsWith(it)) ?? "$$$unknown$$$";
670
- yield* (await doProcessFile(repoRoot, file, revisionBoundary)).map((it) => it.concat(clusterPath));
671
- } catch (e) {
672
- if (e.signal === "SIGINT") sigintCaught = true;
673
- }
628
+ const currentFile = filesShuffled[i];
629
+ progress?.setProgress("File", i + 1, files.length);
630
+ progress?.setMessage("File", currentFile.file);
631
+ yield {
632
+ repo: path5.basename(absoluteRepoPath),
633
+ file: currentFile.file,
634
+ rev: revisionBoundary,
635
+ cluster: currentFile.cluster
636
+ };
674
637
  }
675
638
  process.stderr.write(" ".repeat(process.stderr.columns || 80) + "\r");
676
639
  console.error(`Analysis complete for '${repoName}'.`);
677
640
  }
678
- function daysAgo(epoch) {
679
- const now = Date.now();
680
- const diff = now - epoch * 1e3;
681
- return Math.floor(diff / (1e3 * 60 * 60 * 24));
682
- }
683
- function bucket(n, buckets) {
684
- for (let i = 1; i < buckets.length; i++) {
685
- if (n > buckets[i - 1] && n < buckets[i]) return buckets[i - 1];
686
- }
687
- return -1;
688
- }
689
- async function doProcessFile1(repoRoot, filePath, revisionBoundary) {
690
- if (!filePath) return [];
691
- const absPath = path4.join(repoRoot, filePath);
641
+ async function doProcessFile(absoluteRepoRoot, repoRelativeFilePath, revisionBoundary) {
642
+ if (!repoRelativeFilePath) return [];
643
+ const absoluteFilePath = path5.join(absoluteRepoRoot, repoRelativeFilePath);
692
644
  let stat = null;
693
645
  try {
694
- stat = fs4.statSync(absPath);
646
+ stat = fs5.statSync(absoluteFilePath);
695
647
  } catch (e) {
696
- console.error(`Fail get stats for file ${absPath}`, e.stack || e.message || e);
648
+ console.error(`Fail get stats for file ${absoluteFilePath}`, e.stack || e.message || e);
697
649
  }
698
650
  if (!stat || !stat.isFile() || stat.size === 0) return [];
699
651
  const result = [];
700
- for (const item of await git_blame_porcelain(filePath, repoRoot, ["author", "committer-time", "commit"], revisionBoundary + "..HEAD")) {
701
- if (revisionBoundary === item[2]) {
702
- item[0] = "?";
703
- item[1] = 0;
704
- item[2] = "0".repeat(40);
705
- }
706
- const lang = path4.extname(filePath) || "Other";
707
- let days_bucket = bucket(daysAgo(item[1]), [0, 30, 300, 1e3, 1e6]);
708
- if (days_bucket != -1) {
709
- result.push([item[0], days_bucket, lang, filePath, repoRoot]);
652
+ for (const item of await git_blame_porcelain(repoRelativeFilePath, absoluteRepoRoot, ["author", "committer-time", "commit"], !!revisionBoundary ? revisionBoundary + "..HEAD" : void 0)) {
653
+ if (revisionBoundary === item.commit) {
654
+ item.author = "Legacy";
655
+ item.time = 0;
656
+ item.commit = "0".repeat(40);
710
657
  }
658
+ result.push({
659
+ repo: absoluteRepoRoot,
660
+ file: path5.basename(repoRelativeFilePath),
661
+ author: item.author,
662
+ commit: item.commit,
663
+ time: item.time,
664
+ year: new Date(item.time * 1e3).getFullYear(),
665
+ month: new Date(item.time * 1e3).getMonth() + 1,
666
+ lang: path5.extname(repoRelativeFilePath) || "Other"
667
+ });
711
668
  }
712
669
  return result;
713
670
  }
714
- async function* distinctCount(source) {
715
- const map = /* @__PURE__ */ new Map();
716
- for await (const row of source) {
717
- const key = JSON.stringify(row);
718
- if (map.has(key)) {
719
- map.get(key).count += 1;
720
- } else {
721
- map.set(key, { row, count: 1 });
722
- }
723
- }
724
- for (const { row, count } of map.values()) {
725
- yield [...row, count];
726
- }
727
- }
728
- function getRepoPathsToProcess(inputPaths) {
729
- let repoPathsToProcess = inputPaths.flatMap((it) => findRepositories(it, 3));
730
- repoPathsToProcess = [...new Set(repoPathsToProcess)].sort();
731
- if (repoPathsToProcess.length === 0) {
732
- throw new Error("No git repositories found to analyze.");
733
- }
734
- console.error(`Found ${repoPathsToProcess.length} repositories to analyze:`);
735
- repoPathsToProcess.forEach((p) => console.error(`- ${p || "."}`));
736
- return repoPathsToProcess;
671
+ async function runScan1(args) {
672
+ const inputPaths = args && args.length > 0 ? args : ["."];
673
+ let repoPathsToProcess = getRepoPathsToProcess(inputPaths);
674
+ await dataDir.write("known_repositories.txt", repoPathsToProcess.join("\n") + "\n");
675
+ let dataSet = streamOf(AsyncGeneratorUtil.of(repoPathsToProcess)).flatMap((repoRelativePath) => getRepositoryFiles(repoRelativePath)).flatMap((fileInfo) => stream.ofArrayPromise(doProcessFile(fileInfo.repo, fileInfo.file, fileInfo.rev)).get()).map((it) => [it.author, it.time, it.lang, it.cluster, it.repo]).get();
676
+ return distinctCount(dataSet);
737
677
  }
738
678
  async function runScan(args) {
739
- const tmpVfs = new RealFileSystemImpl("./.git-stats/");
679
+ let [keys, paths] = extractArgKeys(args);
680
+ progress = new Progress();
681
+ progress.showProgress(300);
740
682
  process.on("SIGINT", () => {
741
683
  if (sigintCaught) {
742
684
  console.error("\nForcing exit.");
@@ -745,23 +687,24 @@ async function runScan(args) {
745
687
  sigintCaught = true;
746
688
  console.error("\nSignal received. Finishing current file then stopping. Press Ctrl+C again to exit immediately.");
747
689
  });
748
- const inputPaths = args && args.length > 0 ? args : ["."];
749
- let repoPathsToProcess = getRepoPathsToProcess(inputPaths);
750
- await tmpVfs.write("data.jsonl", "");
751
- let dataSet = new AsyncIteratorWrapperImpl(AsyncGeneratorUtil.of(repoPathsToProcess)).flatMap((repoPath) => forEachRepoFile(repoPath, doProcessFile1)).map((it) => [it[0], it[1], it[2], it[5], path4.basename(it[4])]).get();
752
- let aggregatedData1 = distinctCount(dataSet);
690
+ let aggregatedData1 = await runScan1(paths);
753
691
  let aggregatedData = await AsyncGeneratorUtil.collect(aggregatedData1);
754
- aggregatedData.forEach((it) => console.log(JSON.stringify(it)));
692
+ progress.destroy();
693
+ if (keys.includes("stdout")) {
694
+ aggregatedData.forEach((it) => console.log(JSON.stringify(it)));
695
+ } else {
696
+ aggregatedData.forEach((it) => dataDir.append("data.jsonl", JSON.stringify(it) + "\n"));
697
+ }
755
698
  }
756
699
  async function runHtml(args) {
757
- const inputPath = args[0] || path4.resolve("./.git-stats/data.jsonl");
758
- const outHtml = path4.resolve("./.git-stats/report.html");
759
- if (!fs4.existsSync(inputPath)) {
760
- console.error(`Input data file not found: ${inputPath}`);
700
+ const absoluteInputPath = args[0] || path5.resolve("./.git-stats/data.jsonl");
701
+ const absoluteOutHtml = path5.resolve("./.git-stats/report.html");
702
+ if (!fs5.existsSync(absoluteInputPath)) {
703
+ console.error(`Input data file not found: ${absoluteInputPath}`);
761
704
  process.exitCode = 1;
762
705
  return;
763
706
  }
764
- const lines = fs4.readFileSync(inputPath, "utf8").split(/\r?\n/).filter(Boolean);
707
+ const lines = fs5.readFileSync(absoluteInputPath, "utf8").split(/\r?\n/).filter(Boolean);
765
708
  const aggregatedData = lines.map((line) => {
766
709
  try {
767
710
  return JSON.parse(line);
@@ -769,29 +712,92 @@ async function runHtml(args) {
769
712
  return null;
770
713
  }
771
714
  }).filter(Boolean);
772
- generateHtmlReport(aggregatedData, outHtml);
773
- console.error(`HTML report generated: ${outHtml}`);
715
+ generateHtmlReport(aggregatedData, absoluteOutHtml);
716
+ console.error(`HTML report generated: ${absoluteOutHtml}`);
774
717
  }
775
- function findRepositories(path5, depth) {
776
- if (depth <= 0) return [];
777
- if (!fs4.existsSync(path5)) throw new Error(`Path does not exist: ${path5}`);
778
- if (!fs4.statSync(path5).isDirectory()) return [];
779
- if (isGitRepo(path5)) return [path5];
780
- let result = getDirectories(path5).flatMap((dir) => findRepositories(dir, depth - 1));
781
- return [...new Set(result)].sort();
718
+ async function* forEachStdinLine(consumer) {
719
+ const rl = readline.createInterface({
720
+ input: process.stdin,
721
+ output: process.stdout,
722
+ terminal: false
723
+ });
724
+ for await (const line of rl) {
725
+ if (!line.trim()) continue;
726
+ try {
727
+ consumer(line);
728
+ } catch (error) {
729
+ console.error(`Error parsing line: ${line}`, error);
730
+ }
731
+ }
732
+ yield null;
733
+ }
734
+ async function runSlice(args) {
735
+ let cols = args.map((it) => parseInt(it));
736
+ let result = {};
737
+ await forEachStdinLine((it) => {
738
+ const data = JSON.parse(it);
739
+ console.log(data);
740
+ let n = result;
741
+ for (let col of cols) {
742
+ n[data[col]] = n?.[data[col]] ?? {};
743
+ n = n[data[col]];
744
+ n.count = (n?.count ?? 0) + data[data.length - 1];
745
+ n.values = n?.values ?? {};
746
+ n = n.values;
747
+ }
748
+ }).next();
749
+ console.log(JSON.stringify(result, null, 2));
750
+ }
751
+ function extractArgKeys(args) {
752
+ const keys = [];
753
+ const values = [];
754
+ for (let i = 0; i < args.length; i++) {
755
+ const arg = args[i];
756
+ if (arg.startsWith("--")) {
757
+ keys.push(arg.substring(2));
758
+ } else {
759
+ values.push(arg);
760
+ }
761
+ }
762
+ return [keys, values];
782
763
  }
783
764
  async function main() {
784
765
  const argv = process.argv.slice(2);
785
- const isHtml = argv[0] === "html";
786
- const subArgs = isHtml ? argv.slice(1) : argv;
787
- if (isHtml) {
788
- await runHtml(subArgs);
789
- } else {
790
- await runScan(subArgs);
766
+ let subcommand = argv[0];
767
+ let subcommandsMenu = {
768
+ "html": {
769
+ description: "Generates an HTML report from the aggregated data.",
770
+ usage: "git-stats html [input-data-file]"
771
+ },
772
+ "scan": {
773
+ description: "Scans a directory tree for Git repositories and generates aggregated data.",
774
+ usage: "git-stats scan [input-dir] > {output-file}.jsonl"
775
+ }
776
+ };
777
+ if (subcommand === "scan") {
778
+ await runScan(argv.slice(1));
779
+ return;
780
+ }
781
+ if (subcommand === "html") {
782
+ await runHtml(argv.slice(1));
783
+ return;
784
+ }
785
+ if (subcommand === "slice") {
786
+ await runSlice(argv.slice(1));
787
+ return;
788
+ }
789
+ console.error(`Usage: git-stats <subcommand> [args]
790
+
791
+ Available subcommands:`);
792
+ for (const [name, { description, usage }] of Object.entries(subcommandsMenu)) {
793
+ console.error(`- ${name}: ${description}
794
+ Usage: ${usage}`);
791
795
  }
792
796
  }
793
797
  main().catch(console.error);
794
798
  // Annotate the CommonJS export names for ESM import in node:
795
799
  0 && (module.exports = {
796
- distinctCount
800
+ dataDir,
801
+ progress,
802
+ runScan1
797
803
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vladimirshefer/git-stats",
3
- "version": "0.0.1",
3
+ "version": "0.8.0",
4
4
  "description": "A CLI to generate git blame stats for a repository",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -21,8 +21,9 @@
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/node": "^20.0.0",
24
+ "@vladimirshefer/git-stats--html-ui": "*",
24
25
  "vitest": "^1.6.0",
25
- "esbuild": "0.25.0",
26
+ "esbuild": "0.25.2",
26
27
  "typescript": "^5.0.0"
27
28
  }
28
29
  }