@vladimirshefer/git-stats 0.0.2 → 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 +309 -633
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -6,6 +6,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
9
13
  var __copyProps = (to, from, except, desc) => {
10
14
  if (from && typeof from === "object" || typeof from === "function") {
11
15
  for (let key of __getOwnPropNames(from))
@@ -22,542 +26,31 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
26
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
27
  mod
24
28
  ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
25
30
 
26
31
  // src/index.ts
27
- var fs3 = __toESM(require("fs"));
28
- var path3 = __toESM(require("path"));
32
+ var index_exports = {};
33
+ __export(index_exports, {
34
+ dataDir: () => dataDir,
35
+ progress: () => progress,
36
+ runScan1: () => runScan1
37
+ });
38
+ module.exports = __toCommonJS(index_exports);
39
+ var fs5 = __toESM(require("fs"));
40
+ var path5 = __toESM(require("path"));
41
+ var readline = __toESM(require("readline"));
29
42
 
30
43
  // src/output/report_template.ts
31
44
  var path = __toESM(require("path"));
32
45
  var fs = __toESM(require("fs"));
33
46
 
34
- // src/output/report_template.html
35
- var report_template_default = `<!DOCTYPE html>
36
- <!--suppress TypeScriptMissingConfigOption -->
37
- <html lang="en">
38
- <head>
39
- <meta charset="UTF-8">
40
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
41
- <title>Git Blame Statistics</title>
42
- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
43
- <script src="https://cdn.plot.ly/plotly-3.3.0.min.js" charset="utf-8"></script>
44
- <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
45
- <!-- Preact UMD build -->
46
- <script crossorigin src="https://unpkg.com/preact@10/dist/preact.umd.js"></script>
47
- <script crossorigin src="https://unpkg.com/preact@10/hooks/dist/hooks.umd.js"></script>
48
- <!-- Babel Standalone for in-browser JSX transform -->
49
- <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
50
- </head>
51
- <body class="font-sans m-0 bg-gray-50 text-gray-900">
52
- <div id="root"></div>
53
- </body>
54
- <script type="text/babel" data-presets="react">
55
- // Preact compat aliases
56
- const React = window.preact;
57
- const ReactDOM = {
58
- createRoot: (container) => ({
59
- render: (element) => window.preact.render(element, container)
60
- })
61
- };
62
- React.useRef = window.preactHooks.useRef;
63
- React.useEffect = window.preactHooks.useEffect;
64
- React.useState = window.preactHooks.useState;
65
- React.useMemo = window.preactHooks.useMemo;
66
-
67
- const RAW_DATASET =
68
- __DATASET_JSON__
69
- || [[1,2,3,4,5,6,7,8,9]];
70
- // Fixed schema as per pipeline: [author, days_bucket, lang, clusterPath, repoName, count]
71
- const RAW_DATASET_SCHEMA = ["author", "days_bucket", "lang", "clusterPath", "repoName", "count"];
72
- const CLUSTER_COLUMN = RAW_DATASET_SCHEMA.indexOf("clusterPath");
73
- const REPO_COLUMN = RAW_DATASET_SCHEMA.indexOf("repoName");
74
- // -------- Client-side grouping/filtering engine --------
75
- const KEY_INDEX = Object.fromEntries(RAW_DATASET_SCHEMA.map((k, i) => [k, i]));
76
- const COLUMNS_AMOUNT = RAW_DATASET[0].length - 1;
77
- const COLUMNS_IDX_ARRAY = Array(COLUMNS_AMOUNT).fill(-1).map((_, i) => i);
78
- const COUNT_IDX_FROM_END = 1; // last element is count
79
- const COLUMN_COMPARATORS = COLUMNS_IDX_ARRAY.map(idx => {
80
- let isNumber = typeof RAW_DATASET?.[0]?.[idx] === "number";
81
- return isNumber ? ((a, b) => (a || 0) - (b || 0)) : (a, b) => String(a).localeCompare(String(b));
82
- })
83
- const UNIQUE_VALUES = COLUMNS_IDX_ARRAY.map((idx) => uniqueValues(RAW_DATASET, idx).sort(COLUMN_COMPARATORS[idx]))
84
-
85
- const TOP_N = 20;
86
- const BUCKET_COLORS = [
87
- 'rgba(214, 40, 40, 0.7)',
88
- 'rgba(247, 127, 0, 0.7)',
89
- 'rgba(252, 191, 73, 0.7)',
90
- 'rgba(168, 218, 142, 0.7)',
91
- 'rgba(75, 192, 192, 0.7)',
92
- 'rgba(54, 162, 235, 0.7)',
93
- 'rgba(153, 102, 255, 0.7)',
94
- 'rgba(201, 203, 207, 0.7)'
95
- ];
96
-
97
- function uniqueValues(arr, idx) {
98
- const set = new Set(arr.map(r => r[idx]));
99
- return Array.from(set);
100
- }
101
-
102
- function matchesFilters(row, filters) {
103
- for (let idx = 0; idx < COLUMNS_AMOUNT; idx++) {
104
- const sel = filters[idx];
105
- if (sel && !sel.has(String(row[idx]))) {
106
- return false;
107
- }
108
- }
109
- return true;
110
- }
111
-
112
- function pivot(
113
- dataset,
114
- column1,
115
- column2
116
- ) {
117
- const grouped2 = new Map();
118
- const secValuesSet = new Set();
119
- for (const row of dataset) {
120
- const c1 = row[column1];
121
- const c2 = row[column2];
122
- const count = Number(row[row.length - COUNT_IDX_FROM_END]) || 0;
123
- if (!grouped2.has(c1)) grouped2.set(c1, new Map());
124
- grouped2.get(c1).set(c2, (grouped2.get(c1).get(c2) || 0) + count);
125
- secValuesSet.add(c2);
126
- }
127
- const primaryTotals = new Map();
128
- for (const [c1, innerMap] of grouped2) {
129
- let total = 0;
130
- for (const val of innerMap.values()) total += val;
131
- primaryTotals.set(c1, total);
132
- }
133
- const primaryKeys = Array.from(grouped2.keys()).sort((a, b) => (primaryTotals.get(b) || 0) - (primaryTotals.get(a) || 0));
134
- const secondaryKeys = Array.from(secValuesSet).sort((a, b) => String(a).localeCompare(String(b)));
135
- return {grouped2, primaryKeys, secondaryKeys};
136
- }
137
-
138
- function computeColumnTotals(dataset, columnIdx) {
139
- const value2total = new Map();
140
- const otherColumnContributions = new Map(); // Map<key, Map<otherColumnIdx, Map<otherValue, count>>>
141
-
142
- for (const row of dataset) {
143
- const value = row[columnIdx];
144
- const count = Number(row[row.length - 1]) || 0;
145
- value2total.set(value, (value2total.get(value) || 0) + count);
146
-
147
- // Track contributions from other columns
148
- if (!otherColumnContributions.has(value)) {
149
- otherColumnContributions.set(value, new Map());
150
- }
151
- const keyContribs = otherColumnContributions.get(value);
152
-
153
- for (let otherIdx = 0; otherIdx < COLUMNS_AMOUNT; otherIdx++) {
154
- if (otherIdx === columnIdx) continue;
155
-
156
- if (!keyContribs.has(otherIdx)) {
157
- keyContribs.set(otherIdx, new Map());
158
- }
159
- const otherColMap = keyContribs.get(otherIdx);
160
- const otherValue = row[otherIdx];
161
- otherColMap.set(otherValue, (otherColMap.get(otherValue) || 0) + count);
162
- }
163
- }
164
-
165
- const sorted = Array.from(value2total.entries()).sort((a, b) => b[1] - a[1]);
166
-
167
- return {
168
- keys: sorted.map(([k]) => k),
169
- values: sorted.map(([, v]) => v),
170
- totals: value2total,
171
- contributions: otherColumnContributions
172
- };
173
- }
174
-
175
- // ---------- React Components ----------
176
- function ColumnTotalCard({columnName, columnIdx, keys, values, totals, contributions}) {
177
- const containerRef = React.useRef(null);
178
-
179
- const {labels, hoverText} = React.useMemo(() => {
180
- const labels = [];
181
- const hoverText = [];
182
-
183
- keys.forEach((k) => {
184
- const total = totals.get(k);
185
- const keyContribs = contributions.get(k);
186
- const topContribs = [];
187
-
188
- for (let otherIdx = 0; otherIdx < COLUMNS_AMOUNT; otherIdx++) {
189
- if (otherIdx === columnIdx) continue;
190
- const otherColMap = keyContribs?.get(otherIdx);
191
- if (otherColMap) {
192
- const top3 = Array.from(otherColMap.entries())
193
- .sort((a, b) => b[1] - a[1])
194
- .slice(0, 3)
195
- .map(([val, cnt]) => \`\${val}(\${(cnt / total * 100.0).toFixed(1)}%)\`)
196
- .join('<br>-');
197
- if (top3) {
198
- topContribs.push(\`\${RAW_DATASET_SCHEMA[otherIdx]}<br>-\` + top3);
199
- }
200
- }
201
- }
202
-
203
- const label = \`\${String(k)} (\${total})\`;
204
- labels.push(label);
205
- hoverText.push(topContribs.length > 0 ? \`\${label}<br>\${topContribs.join('<br>')}\` : label);
206
- });
207
-
208
- return {labels, hoverText};
209
- }, [columnName, columnIdx, keys, totals, contributions]);
210
-
211
- React.useEffect(() => {
212
- const el = containerRef.current;
213
- if (!el) return;
214
- if (!labels.length) {
215
- el.innerHTML = '<div class="text-gray-500 py-6 text-center">No data to display</div>';
216
- return;
217
- }
218
- const trace = {
219
- type: 'sunburst',
220
- labels: labels,
221
- parents: labels.map(() => ''),
222
- values: values,
223
- hovertext: hoverText,
224
- hovertemplate: '%{hovertext}<extra></extra>',
225
- branchvalues: 'total'
226
- };
227
- const layout = {
228
- margin: {l: 0, r: 0, t: 10, b: 10},
229
- sunburstcolorway: ['#4F46E5', '#10B981', '#F59E0B', '#EF4444', '#06B6D4', '#8B5CF6', '#F43F5E'],
230
- extendsunburstcolors: true,
231
- height: 300
232
- };
233
- const config = {responsive: true, displayModeBar: false};
234
- if (window.Plotly && window.Plotly.newPlot) {
235
- window.Plotly.newPlot(el, [trace], layout, config);
236
- }
237
- }, [labels, hoverText, values]);
238
-
239
- return (
240
- <div className="bg-white border border-gray-200 rounded-lg p-4 shadow-sm">
241
- <h3 className="text-lg font-semibold mb-3 text-gray-800">{columnName} - Total</h3>
242
- <div ref={containerRef} className="w-full"/>
243
- </div>
244
- );
245
- }
246
-
247
- // ---------- Sunburst over repository paths (clusterPath) ----------
248
- function buildSunburst(dataset, filterState) {
249
- const filtered = dataset.filter(row => matchesFilters(row, filterState));
250
-
251
- const PSEUDO_ROOT = "@";
252
- // Aggregate counts per path and propagate sums up the tree
253
- const leafSums = new Map(); // fullPath -> sum
254
- const allPaths = new Set(); // includes all prefixes (no empty)
255
-
256
- for (const row of filtered) {
257
- const rawPath = String(row[REPO_COLUMN] + "/" + row[CLUSTER_COLUMN] ?? '').trim();
258
- const cnt = Number(row[COLUMNS_AMOUNT]) || 0;
259
- if (!rawPath) continue;
260
- const segs = rawPath.split('/').filter(s => s && s !== '.');
261
- if (segs.length === 0) continue;
262
- const full = segs.join('/');
263
- leafSums.set(full, (leafSums.get(full) || 0) + cnt);
264
- // Collect all prefix nodes for structure
265
- for (let i = 0; i < segs.length; i++) {
266
- const p = segs.slice(0, i + 1).join('/');
267
- allPaths.add(p);
268
- }
269
- }
270
-
271
- if (allPaths.size === 0) return {ids: [], labels: [], parents: [], values: []};
272
-
273
- // Compute total values for every node as sum of its descendant leaves
274
- const totals = new Map(Array.from(allPaths, p => [p, 0]));
275
- for (const [leaf, v] of leafSums) {
276
- const segs = leaf.split('/');
277
- for (let i = 0; i < segs.length; i++) {
278
- const p = segs.slice(0, i + 1).join('/');
279
- totals.set(p, (totals.get(p) || 0) + v);
280
- }
281
- }
282
-
283
- // Build Plotly arrays
284
- const ids = [];
285
- const labels = [];
286
- const parents = [];
287
- const values = [];
288
-
289
- // Ensure stable ordering: sort by path length then alphabetically
290
- const ordered = Array.from(allPaths);
291
- ordered.sort((a, b) => {
292
- const da = a.split('/').length, db = b.split('/').length;
293
- if (da !== db) return da - db;
294
- return a.localeCompare(b);
295
- });
296
-
297
- for (const id of ordered) {
298
- const segs = id.split('/');
299
- const label = segs[segs.length - 1] || id;
300
- const parent = segs.length > 1 ? segs.slice(0, -1).join('/') : '';
301
- ids.push(id);
302
- labels.push(label);
303
- parents.push(parent);
304
- values.push(totals.get(id) || 0);
305
- }
306
-
307
- return {ids, labels, parents, values};
308
- }
309
-
310
- function SunburstPaths({dataset, filters}) {
311
- const containerRef = React.useRef(null);
312
- const data = React.useMemo(() => buildSunburst(dataset, filters), [dataset, filters]);
313
-
314
- React.useEffect(() => {
315
- const el = containerRef.current;
316
- if (!el) return;
317
- if (!data.ids.length) {
318
- el.innerHTML = '<div class="text-gray-500 py-6 text-center">No path data to display</div>';
319
- return;
320
- }
321
- const trace = {
322
- type: 'sunburst',
323
- ids: data.ids,
324
- labels: data.labels,
325
- parents: data.parents,
326
- values: data.values,
327
- branchvalues: 'total',
328
- maxdepth: 3
329
- };
330
- const layout = {
331
- margin: {l: 0, r: 0, t: 10, b: 10},
332
- sunburstcolorway: ['#4F46E5', '#10B981', '#F59E0B', '#EF4444', '#06B6D4', '#8B5CF6', '#F43F5E'],
333
- extendsunburstcolors: true,
334
- height: 400
335
- };
336
- const config = {responsive: true, displayModeBar: false};
337
- // Use Plotly from global
338
- if (window.Plotly && window.Plotly.newPlot) {
339
- window.Plotly.newPlot(el, [trace], layout, config);
340
- }
341
- }, [data]);
342
-
343
- return <div ref={containerRef} className="w-full"/>;
344
- }
345
-
346
- function MultiSelect({label, values, selectedSet, onChange}) {
347
- const [isOpen, setIsOpen] = React.useState(false);
348
- const [searchTerm, setSearchTerm] = React.useState('');
349
- const dropdownRef = React.useRef(null);
350
-
351
- React.useEffect(() => {
352
- function handleClickOutside(event) {
353
- if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
354
- setIsOpen(false);
355
- }
356
- }
357
-
358
- document.addEventListener('mousedown', handleClickOutside);
359
- return () => document.removeEventListener('mousedown', handleClickOutside);
360
- }, []);
361
-
362
- const filteredValues = React.useMemo(() => {
363
- if (!searchTerm) return values;
364
- const lower = searchTerm.toLowerCase();
365
- return values.filter(v => String(v).toLowerCase().includes(lower));
366
- }, [values, searchTerm]);
367
-
368
- const selCount = selectedSet.size;
369
- const total = values.length;
370
-
371
- const toggleValue = (val) => {
372
- const newSet = new Set(selectedSet);
373
- if (newSet.has(val)) {
374
- newSet.delete(val);
375
- } else {
376
- newSet.add(val);
377
- }
378
- onChange(newSet);
379
- };
380
-
381
- return (
382
- <div ref={dropdownRef} className="relative">
383
- <label className="block font-semibold mb-1.5">
384
- {label}{' '}
385
- <span className="text-gray-600 font-normal">({selCount}/{total})</span>
386
- </label>
387
- <button
388
- type="button"
389
- onClick={() => setIsOpen(!isOpen)}
390
- 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"
391
- >
392
- <span className="text-gray-700">
393
- {selCount === 0 ? 'Select...' : selCount === total ? 'All selected' : \`\${selCount} selected\`}
394
- </span>
395
- <span className="float-right">\u25BC</span>
396
- </button>
397
- {isOpen && (
398
- <div
399
- className="absolute z-10 w-full mt-1 bg-white border border-gray-300 rounded-md shadow-lg max-h-80 overflow-hidden">
400
- <div className="p-2 border-b border-gray-200">
401
- <input
402
- type="text"
403
- placeholder="Search..."
404
- value={searchTerm}
405
- onChange={(e) => setSearchTerm(e.target.value)}
406
- 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"
407
- onClick={(e) => e.stopPropagation()}
408
- />
409
- <div className="flex gap-2 mt-2">
410
- <button
411
- type="button"
412
- onClick={() => onChange(new Set(values.map(String)))}
413
- 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"
414
- >
415
- Select All
416
- </button>
417
- <button
418
- type="button"
419
- onClick={() => onChange(new Set())}
420
- 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"
421
- >
422
- Unselect All
423
- </button>
424
- </div>
425
- </div>
426
- <div className="overflow-y-auto max-h-64">
427
- {filteredValues.length === 0 ? (
428
- <div className="px-3 py-2 text-gray-500 text-center">No matches found</div>
429
- ) : (
430
- filteredValues.map(v => {
431
- const val = String(v);
432
- const isChecked = selectedSet.has(val);
433
- return (
434
- <label
435
- key={val}
436
- className="flex items-center px-3 py-2 hover:bg-gray-100 cursor-pointer"
437
- >
438
- <input
439
- type="checkbox"
440
- checked={isChecked}
441
- onChange={() => toggleValue(val)}
442
- className="mr-2 h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
443
- />
444
- <span className="text-gray-900">{val}</span>
445
- </label>
446
- );
447
- })
448
- )}
449
- </div>
450
- </div>
451
- )}
452
- </div>
453
- );
454
- }
455
-
456
- function App() {
457
- const {initialFilters, valueOptions} = React.useMemo(() => {
458
- const filters = {};
459
- const options = {};
460
- for (let idx = 0; idx < COLUMNS_AMOUNT; idx++) {
461
- filters[idx] = new Set(UNIQUE_VALUES[idx].map(v => String(v)));
462
- options[idx] = UNIQUE_VALUES[idx];
463
- }
464
- return {initialFilters: filters, valueOptions: options};
465
- }, []);
466
- const [filters, setFilters] = React.useState(initialFilters);
467
- const [primaryKeyIndex, setPrimaryKeyIndex] = React.useState(0);
468
- const [secondaryKeyIndex, setSecondaryKeyIndex] = React.useState(1);
469
-
470
- const {chartLabels, datasets} = React.useMemo(() => {
471
- const filteredDataset = RAW_DATASET.filter(row => matchesFilters(row, filters));
472
- const {
473
- grouped2: pv,
474
- primaryKeys,
475
- secondaryKeys
476
- } = pivot(filteredDataset, primaryKeyIndex, secondaryKeyIndex);
477
- const chartPrimaryKeys = primaryKeys.slice(0, TOP_N);
478
- const ds = secondaryKeys.map((sk, i) => ({
479
- label: String(sk),
480
- data: chartPrimaryKeys.map(pk => (pv.get(pk)?.get(sk)) || 0),
481
- backgroundColor: BUCKET_COLORS[i % BUCKET_COLORS.length]
482
- }));
483
- return {chartLabels: chartPrimaryKeys, datasets: ds};
484
- }, [filters, primaryKeyIndex, secondaryKeyIndex]);
485
-
486
- return (
487
- <div className="max-w-4xl mx-auto my-5 p-5 bg-white rounded-lg shadow-sm">
488
- <h1 className="border-b border-gray-300 pb-2.5">Git Contribution Statistics</h1>
489
- <div className="controls">
490
- <h2 className="border-b border-gray-300 pb-2.5">Controls</h2>
491
- <div className="flex gap-4 flex-wrap items-center">
492
- <label>
493
- Primary group:
494
- <select value={primaryKeyIndex} onChange={e => setPrimaryKeyIndex(Number(e.target.value))}>
495
- {COLUMNS_IDX_ARRAY.map((__, i) => (
496
- <option key={i} value={i}>{RAW_DATASET_SCHEMA[i]}</option>
497
- ))}
498
- </select>
499
- </label>
500
- <label>
501
- Secondary group:
502
- <select value={secondaryKeyIndex}
503
- onChange={e => setSecondaryKeyIndex(Number(e.target.value))}>
504
- {COLUMNS_IDX_ARRAY.map((__, i) => (
505
- <option key={i} value={i}>{RAW_DATASET_SCHEMA[i]}</option>
506
- ))}
507
- </select>
508
- </label>
509
- </div>
510
- <div id="filters" className="mt-3 grid grid-cols-[repeat(auto-fit,minmax(220px,1fr))] gap-3">
511
- {COLUMNS_IDX_ARRAY.map((__, idx) => (
512
- <MultiSelect
513
- key={idx}
514
- label={RAW_DATASET_SCHEMA[idx]}
515
- values={valueOptions[idx]}
516
- selectedSet={filters[idx]}
517
- onChange={(newSet) => setFilters(prev => ({...prev, [idx]: newSet}))}
518
- />
519
- ))}
520
- </div>
521
- </div>
522
- <div className="mt-8">
523
- <h2 className="border-b border-gray-300 pb-2.5">Column Totals</h2>
524
- <div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
525
- {COLUMNS_IDX_ARRAY.map((__, idx) => {
526
- const filteredDataset = RAW_DATASET.filter(row => matchesFilters(row, filters));
527
- const {keys, values, totals, contributions} = computeColumnTotals(filteredDataset, idx);
528
- return (
529
- <ColumnTotalCard
530
- key={idx}
531
- columnName={RAW_DATASET_SCHEMA[idx]}
532
- columnIdx={idx}
533
- keys={keys}
534
- values={values}
535
- totals={totals}
536
- contributions={contributions}
537
- />
538
- );
539
- })}
540
- </div>
541
- </div>
542
- <div className="mt-8">
543
- <h2 className="border-b border-gray-300 pb-2.5">Repository Paths Sunburst</h2>
544
- <p className="text-sm text-gray-600 mt-2 mb-3">Breakdown by folder structure based on <code>clusterPath</code> within current filters.</p>
545
- <SunburstPaths dataset={RAW_DATASET} filters={filters}/>
546
- </div>
547
- </div>
548
- );
549
- }
550
-
551
- const root = ReactDOM.createRoot(document.getElementById('root'));
552
- root.render(<App/>);
553
- </script>
554
- </html>
555
- `;
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';
556
49
 
557
50
  // src/output/report_template.ts
558
51
  function generateHtmlReport(data, outputFile) {
559
52
  const finalOutputPath = path.join(outputFile);
560
- let htmlContent = report_template_default.split("__DATASET_JSON__");
53
+ let htmlContent = dist_default.split("__DATASET_JSON__");
561
54
  fs.writeFileSync(finalOutputPath, htmlContent[0]);
562
55
  fs.appendFileSync(finalOutputPath, "\n[\n");
563
56
  for (let i = 0; i < data.length; i++) {
@@ -577,7 +70,7 @@ function execAsync(command, args = [], options = {}) {
577
70
  return new Promise((resolve2, reject) => {
578
71
  const child = (0, import_child_process.spawn)(command, args, { ...options });
579
72
  let stdout = [];
580
- let stderr = [];
73
+ let stderr2 = [];
581
74
  let stdoutBuffer = "";
582
75
  let stderrBuffer = "";
583
76
  child.stdout.on("data", (data) => {
@@ -592,7 +85,7 @@ function execAsync(command, args = [], options = {}) {
592
85
  stderrBuffer += data.toString();
593
86
  const lines = stderrBuffer.split("\n");
594
87
  for (let i = 0; i < lines.length - 1; i++) {
595
- stderr.push(lines[i]);
88
+ stderr2.push(lines[i]);
596
89
  }
597
90
  stderrBuffer = lines[lines.length - 1];
598
91
  });
@@ -604,13 +97,13 @@ function execAsync(command, args = [], options = {}) {
604
97
  stdout.push(stdoutBuffer);
605
98
  }
606
99
  if (stderrBuffer.length > 0) {
607
- stderr.push(stderrBuffer);
100
+ stderr2.push(stderrBuffer);
608
101
  }
609
102
  if (code === 0) {
610
- resolve2({ stdout, stderr });
103
+ resolve2({ stdout, stderr: stderr2 });
611
104
  } else {
612
105
  reject(new Error(`Command ${command} ${JSON.stringify(args)} failed with code ${code}
613
- ${stderr.join("\n")}`));
106
+ ${stderr2.join("\n")}`));
614
107
  }
615
108
  });
616
109
  });
@@ -638,18 +131,18 @@ function parsePorcelain(blameOutput, fields) {
638
131
  const commiterTimePos = fields.indexOf("committer-time");
639
132
  const boundaryPos = fields.indexOf("boundary");
640
133
  const commitPos = fields.indexOf("commit");
641
- let emptyRow = [...fields];
134
+ let emptyRow = {};
642
135
  if (commiterTimePos >= 0) {
643
136
  emptyRow[commiterTimePos] = 0;
644
137
  }
645
138
  if (boundaryPos >= 0) {
646
139
  emptyRow[boundaryPos] = 0;
647
140
  }
648
- let nextRow = [...emptyRow];
141
+ let nextRow = { ...emptyRow };
649
142
  const result = [];
650
143
  for (const line of blameOutput) {
651
144
  if (line.startsWith(" ")) {
652
- result.push([...nextRow]);
145
+ result.push({ ...nextRow });
653
146
  continue;
654
147
  }
655
148
  if (commitPos >= 0) {
@@ -657,22 +150,22 @@ function parsePorcelain(blameOutput, fields) {
657
150
  if (firstSpace === 40) {
658
151
  const possibleHash = line.substring(0, firstSpace);
659
152
  if (/^\^?[0-9a-f]{40}$/i.test(possibleHash)) {
660
- nextRow = [...emptyRow];
661
- nextRow[commitPos] = possibleHash.replace(/^\^/, "");
153
+ nextRow = { ...emptyRow };
154
+ nextRow.commit = possibleHash.replace(/^\^/, "");
662
155
  continue;
663
156
  }
664
157
  }
665
158
  }
666
159
  if (userPos >= 0 && line.startsWith("author ")) {
667
- nextRow[userPos] = line.substring("author ".length).replace(/^<|>$/g, "");
160
+ nextRow.author = line.substring("author ".length).replace(/^<|>$/g, "");
668
161
  continue;
669
162
  }
670
163
  if (commiterTimePos >= 0 && line.startsWith("committer-time ")) {
671
- nextRow[commiterTimePos] = parseInt(line.substring("committer-time ".length), 10);
164
+ nextRow.time = parseInt(line.substring("committer-time ".length), 10);
672
165
  continue;
673
166
  }
674
167
  if (boundaryPos >= 0 && line.startsWith("boundary")) {
675
- nextRow[boundaryPos] = 1;
168
+ nextRow.boundary = 1;
676
169
  }
677
170
  }
678
171
  return result;
@@ -756,6 +249,22 @@ var AsyncIteratorWrapperImpl = class {
756
249
  constructor(source) {
757
250
  this.source = source;
758
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
+ }
759
268
  get() {
760
269
  return this.source;
761
270
  }
@@ -848,7 +357,6 @@ var graph;
848
357
  child.value = [];
849
358
  if (child.size === 0) {
850
359
  delete graphNode.children[k];
851
- console.error("delete", graphNode.path, k);
852
360
  result = true;
853
361
  }
854
362
  }
@@ -868,7 +376,6 @@ var graph;
868
376
  graphNode.value.push(...flatten(child));
869
377
  graphNode.size++;
870
378
  delete graphNode.children[k];
871
- console.error("delete", graphNode.path, k);
872
379
  return true;
873
380
  }
874
381
  return false;
@@ -911,10 +418,10 @@ function clusterFiles(files, clusterMaxSize, clusterMinSize) {
911
418
  }));
912
419
  return subclusters.map((cluster) => {
913
420
  let files2 = cluster.files;
914
- let path4 = cluster.path;
421
+ let path6 = cluster.path;
915
422
  let files1 = files2.map((it) => it.str);
916
423
  return {
917
- path: path4.join("/"),
424
+ path: path6.join("/"),
918
425
  files: files1.sort(),
919
426
  weight: files1.length,
920
427
  isLeftovers: cluster.isLeftovers
@@ -927,38 +434,178 @@ async function* distinctCount(source) {
927
434
  const map = /* @__PURE__ */ new Map();
928
435
  for await (const row of source) {
929
436
  const key = JSON.stringify(row);
437
+ let count = row?.count ?? 1;
930
438
  if (map.has(key)) {
931
- map.get(key).count += 1;
439
+ map.get(key).count += count;
932
440
  } else {
933
- map.set(key, { row, count: 1 });
441
+ map.set(key, { row, count });
934
442
  }
935
443
  }
936
444
  for (const { row, count } of map.values()) {
937
- yield [...row, count];
445
+ yield [row, count];
938
446
  }
939
447
  }
940
448
 
941
- // src/index.ts
942
- var sigintCaught = false;
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
943
483
  function getDirectories(absoluteDirPath) {
944
- if (!fs3.existsSync(absoluteDirPath) || !fs3.statSync(absoluteDirPath).isDirectory()) return [];
484
+ if (!import_fs2.default.existsSync(absoluteDirPath) || !import_fs2.default.statSync(absoluteDirPath).isDirectory()) return [];
945
485
  const ignoredDirs = /* @__PURE__ */ new Set([".git", "node_modules"]);
946
486
  try {
947
- return fs3.readdirSync(absoluteDirPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory() && !ignoredDirs.has(dirent.name)).map((dirent) => path3.join(absoluteDirPath, 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));
948
488
  } catch (error) {
949
489
  console.error(`Could not read directory: ${absoluteDirPath}`);
950
490
  return [];
951
491
  }
952
492
  }
953
- async function* forEachRepoFile(repoRelativePath) {
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);
561
+ }
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);
570
+ }
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) {
954
600
  console.error(`
955
601
  Processing repository: ${repoRelativePath || "."}`);
956
- const absoluteRepoPath = path3.resolve(process.cwd(), repoRelativePath);
957
- const repoName = path3.basename(absoluteRepoPath);
958
- let revisionBoundary = await findRevision(absoluteRepoPath, 5e3);
602
+ const absoluteRepoPath = path5.resolve(process.cwd(), repoRelativePath);
603
+ const repoName = path5.basename(absoluteRepoPath);
604
+ let revisionBoundary = await findRevision(absoluteRepoPath, 1e3);
959
605
  const files = await git_ls_files(absoluteRepoPath, ".");
960
606
  let minClusterSize = Math.floor(Math.max(2, files.length / 100));
961
607
  let maxClusterSize = Math.round(Math.max(20, files.length / 30));
608
+ console.error(`Found ${files.length} files to analyze in '${repoName}'...`);
962
609
  console.error(`Clustering ${files.length} into ${minClusterSize}..${maxClusterSize}+ sized chunks`);
963
610
  const filesClustered = clusterFiles(
964
611
  files,
@@ -966,69 +613,72 @@ Processing repository: ${repoRelativePath || "."}`);
966
613
  minClusterSize
967
614
  );
968
615
  console.error(filesClustered.map((it) => `${it.path} (${it.weight})`));
969
- let clusterPaths = filesClustered.map((it) => it.path);
970
- console.error(`Found ${files.length} files to analyze in '${repoName}'...`);
971
- let filesShuffled = [...files].sort(() => Math.random() - 0.5);
972
- for (let i = 0; i < files.length; i++) {
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++) {
973
627
  if (sigintCaught) break;
974
- const file = filesShuffled[i];
975
- const progressMessage = `[${i + 1}/${files.length}] Analyzing: ${file}`;
976
- process.stderr.write(progressMessage.padEnd(process.stderr.columns || 80, " ") + "\r");
977
- try {
978
- let clusterPath = clusterPaths.find((it) => file.startsWith(it)) ?? "$$$unknown$$$";
979
- yield [absoluteRepoPath, file, revisionBoundary, clusterPath];
980
- } catch (e) {
981
- if (e.signal === "SIGINT") sigintCaught = true;
982
- }
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
+ };
983
637
  }
984
638
  process.stderr.write(" ".repeat(process.stderr.columns || 80) + "\r");
985
639
  console.error(`Analysis complete for '${repoName}'.`);
986
640
  }
987
641
  async function doProcessFile(absoluteRepoRoot, repoRelativeFilePath, revisionBoundary) {
988
642
  if (!repoRelativeFilePath) return [];
989
- const absoluteFilePath = path3.join(absoluteRepoRoot, repoRelativeFilePath);
643
+ const absoluteFilePath = path5.join(absoluteRepoRoot, repoRelativeFilePath);
990
644
  let stat = null;
991
645
  try {
992
- stat = fs3.statSync(absoluteFilePath);
646
+ stat = fs5.statSync(absoluteFilePath);
993
647
  } catch (e) {
994
648
  console.error(`Fail get stats for file ${absoluteFilePath}`, e.stack || e.message || e);
995
649
  }
996
650
  if (!stat || !stat.isFile() || stat.size === 0) return [];
997
651
  const result = [];
998
652
  for (const item of await git_blame_porcelain(repoRelativeFilePath, absoluteRepoRoot, ["author", "committer-time", "commit"], !!revisionBoundary ? revisionBoundary + "..HEAD" : void 0)) {
999
- if (revisionBoundary === item[2]) {
1000
- item[0] = "?";
1001
- item[1] = 0;
1002
- item[2] = "0".repeat(40);
1003
- }
1004
- const lang = path3.extname(repoRelativeFilePath) || "Other";
1005
- let days_bucket = util.yyyyMM(item[1]);
1006
- if (days_bucket != -1) {
1007
- result.push([item[0], days_bucket, lang, repoRelativeFilePath, absoluteRepoRoot]);
1008
- }
653
+ if (revisionBoundary === item.commit) {
654
+ item.author = "Legacy";
655
+ item.time = 0;
656
+ item.commit = "0".repeat(40);
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
+ });
1009
668
  }
1010
669
  return result;
1011
670
  }
1012
- function getRepoPathsToProcess(inputPaths) {
1013
- let repoPathsToProcess = inputPaths.flatMap((it) => findRepositories(it, 3));
1014
- repoPathsToProcess = util.distinct(repoPathsToProcess).sort();
1015
- if (repoPathsToProcess.length === 0) {
1016
- throw new Error("No git repositories found to analyze.");
1017
- }
1018
- console.error(`Found ${repoPathsToProcess.length} repositories to analyze:`);
1019
- repoPathsToProcess.forEach((p) => console.error(`- ${p ?? "."}`));
1020
- return repoPathsToProcess;
1021
- }
1022
- function runScan1(args) {
671
+ async function runScan1(args) {
1023
672
  const inputPaths = args && args.length > 0 ? args : ["."];
1024
673
  let repoPathsToProcess = getRepoPathsToProcess(inputPaths);
1025
- let dataSet = streamOf(AsyncGeneratorUtil.of(repoPathsToProcess)).flatMap((repoRelativePath) => forEachRepoFile(repoRelativePath)).flatMap((fileInfo) => {
1026
- let linesInfo = stream.ofArrayPromise(doProcessFile(fileInfo[0], fileInfo[1], fileInfo[2]));
1027
- return linesInfo.map((it) => it.concat(fileInfo[3])).get();
1028
- }).map((it) => [it[0], it[1], it[2], it[5], path3.basename(it[4])]).get();
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();
1029
676
  return distinctCount(dataSet);
1030
677
  }
1031
678
  async function runScan(args) {
679
+ let [keys, paths] = extractArgKeys(args);
680
+ progress = new Progress();
681
+ progress.showProgress(300);
1032
682
  process.on("SIGINT", () => {
1033
683
  if (sigintCaught) {
1034
684
  console.error("\nForcing exit.");
@@ -1037,19 +687,24 @@ async function runScan(args) {
1037
687
  sigintCaught = true;
1038
688
  console.error("\nSignal received. Finishing current file then stopping. Press Ctrl+C again to exit immediately.");
1039
689
  });
1040
- let aggregatedData1 = runScan1(args);
690
+ let aggregatedData1 = await runScan1(paths);
1041
691
  let aggregatedData = await AsyncGeneratorUtil.collect(aggregatedData1);
1042
- 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
+ }
1043
698
  }
1044
699
  async function runHtml(args) {
1045
- const absoluteInputPath = args[0] || path3.resolve("./.git-stats/data.jsonl");
1046
- const absoluteOutHtml = path3.resolve("./.git-stats/report.html");
1047
- if (!fs3.existsSync(absoluteInputPath)) {
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)) {
1048
703
  console.error(`Input data file not found: ${absoluteInputPath}`);
1049
704
  process.exitCode = 1;
1050
705
  return;
1051
706
  }
1052
- const lines = fs3.readFileSync(absoluteInputPath, "utf8").split(/\r?\n/).filter(Boolean);
707
+ const lines = fs5.readFileSync(absoluteInputPath, "utf8").split(/\r?\n/).filter(Boolean);
1053
708
  const aggregatedData = lines.map((line) => {
1054
709
  try {
1055
710
  return JSON.parse(line);
@@ -1060,13 +715,51 @@ async function runHtml(args) {
1060
715
  generateHtmlReport(aggregatedData, absoluteOutHtml);
1061
716
  console.error(`HTML report generated: ${absoluteOutHtml}`);
1062
717
  }
1063
- function findRepositories(absolutePath, depth) {
1064
- if (depth <= 0) return [];
1065
- if (!fs3.existsSync(absolutePath)) throw new Error(`Path does not exist: ${absolutePath}`);
1066
- if (!fs3.statSync(absolutePath).isDirectory()) return [];
1067
- if (isGitRepo(absolutePath)) return [absolutePath];
1068
- let result = getDirectories(absolutePath).flatMap((dir) => findRepositories(dir, depth - 1));
1069
- return util.distinct(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];
1070
763
  }
1071
764
  async function main() {
1072
765
  const argv = process.argv.slice(2);
@@ -1089,6 +782,10 @@ async function main() {
1089
782
  await runHtml(argv.slice(1));
1090
783
  return;
1091
784
  }
785
+ if (subcommand === "slice") {
786
+ await runSlice(argv.slice(1));
787
+ return;
788
+ }
1092
789
  console.error(`Usage: git-stats <subcommand> [args]
1093
790
 
1094
791
  Available subcommands:`);
@@ -1097,31 +794,10 @@ Available subcommands:`);
1097
794
  Usage: ${usage}`);
1098
795
  }
1099
796
  }
1100
- var util;
1101
- ((util2) => {
1102
- function distinct(arr) {
1103
- return [...new Set(arr)];
1104
- }
1105
- util2.distinct = distinct;
1106
- function daysAgo(epoch) {
1107
- const now = Date.now();
1108
- const diff = now - epoch * 1e3;
1109
- return Math.floor(diff / (1e3 * 60 * 60 * 24));
1110
- }
1111
- util2.daysAgo = daysAgo;
1112
- function bucket(n, buckets) {
1113
- for (let i = 1; i < buckets.length; i++) {
1114
- if (n > buckets[i - 1] && n < buckets[i]) return buckets[i - 1];
1115
- }
1116
- return -1;
1117
- }
1118
- util2.bucket = bucket;
1119
- function yyyyMM(epoch) {
1120
- const date = new Date(epoch * 1e3);
1121
- let yyyyStr = date.getFullYear().toString();
1122
- let MMStr = (date.getMonth() + 1 / 4).toString().padStart(1, "0");
1123
- return parseInt(yyyyStr) * 10 + parseInt(MMStr);
1124
- }
1125
- util2.yyyyMM = yyyyMM;
1126
- })(util || (util = {}));
1127
797
  main().catch(console.error);
798
+ // Annotate the CommonJS export names for ESM import in node:
799
+ 0 && (module.exports = {
800
+ dataDir,
801
+ progress,
802
+ runScan1
803
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vladimirshefer/git-stats",
3
- "version": "0.0.2",
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
  }