@anxin233/gitviz 1.0.3 → 1.1.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.
- package/dist/cli.cjs +254 -21
- package/package.json +1 -1
- package/dist/cli/index.js +0 -67
- package/dist/cli.js +0 -11943
- package/dist/core/analyzer.js +0 -99
- package/dist/core/git-parser.js +0 -53
- package/dist/core/types.js +0 -1
- package/dist/visualizers/html-generator.js +0 -292
package/dist/cli.cjs
CHANGED
|
@@ -11470,15 +11470,63 @@ var GitParser = class {
|
|
|
11470
11470
|
return false;
|
|
11471
11471
|
}
|
|
11472
11472
|
}
|
|
11473
|
-
async
|
|
11474
|
-
|
|
11473
|
+
async getBranches() {
|
|
11474
|
+
try {
|
|
11475
|
+
const branchSummary = await this.git.branch(["-a"]);
|
|
11476
|
+
const branches = [];
|
|
11477
|
+
const seenBranches = /* @__PURE__ */ new Set();
|
|
11478
|
+
for (const branchName of branchSummary.all) {
|
|
11479
|
+
if (branchName.includes("remotes/origin/HEAD"))
|
|
11480
|
+
continue;
|
|
11481
|
+
const cleanName = branchName.replace("remotes/origin/", "").replace(/^\*\s*/, "");
|
|
11482
|
+
if (seenBranches.has(cleanName))
|
|
11483
|
+
continue;
|
|
11484
|
+
seenBranches.add(cleanName);
|
|
11485
|
+
try {
|
|
11486
|
+
const log = await this.git.log({ maxCount: 1, [branchName]: null });
|
|
11487
|
+
if (log.latest) {
|
|
11488
|
+
const commitCount = await this.git.raw(["rev-list", "--count", branchName]);
|
|
11489
|
+
branches.push({
|
|
11490
|
+
name: cleanName,
|
|
11491
|
+
commits: parseInt(commitCount.trim()),
|
|
11492
|
+
lastCommit: new Date(log.latest.date)
|
|
11493
|
+
});
|
|
11494
|
+
}
|
|
11495
|
+
} catch {
|
|
11496
|
+
}
|
|
11497
|
+
}
|
|
11498
|
+
return branches;
|
|
11499
|
+
} catch {
|
|
11500
|
+
return [];
|
|
11501
|
+
}
|
|
11502
|
+
}
|
|
11503
|
+
async parseCommits(limit = 1e3, branch) {
|
|
11504
|
+
const logOptions = {
|
|
11475
11505
|
maxCount: limit,
|
|
11476
11506
|
"--numstat": null,
|
|
11477
11507
|
"--pretty": "format:%H|%an|%ae|%ai|%s"
|
|
11478
|
-
}
|
|
11508
|
+
};
|
|
11509
|
+
if (branch) {
|
|
11510
|
+
logOptions[branch] = null;
|
|
11511
|
+
}
|
|
11512
|
+
const log = await this.git.log(logOptions);
|
|
11479
11513
|
const commits = [];
|
|
11480
11514
|
for (const commit of log.all) {
|
|
11481
|
-
|
|
11515
|
+
let diffSummary;
|
|
11516
|
+
try {
|
|
11517
|
+
diffSummary = await this.git.diffSummary([`${commit.hash}^`, commit.hash]);
|
|
11518
|
+
} catch {
|
|
11519
|
+
try {
|
|
11520
|
+
diffSummary = await this.git.diffSummary(["4b825dc642cb6eb9a060e54bf8d69288fbee4904", commit.hash]);
|
|
11521
|
+
} catch {
|
|
11522
|
+
diffSummary = {
|
|
11523
|
+
files: [],
|
|
11524
|
+
insertions: 0,
|
|
11525
|
+
deletions: 0,
|
|
11526
|
+
changed: 0
|
|
11527
|
+
};
|
|
11528
|
+
}
|
|
11529
|
+
}
|
|
11482
11530
|
commits.push({
|
|
11483
11531
|
hash: commit.hash,
|
|
11484
11532
|
author: commit.author_name || "Unknown",
|
|
@@ -11487,7 +11535,8 @@ var GitParser = class {
|
|
|
11487
11535
|
message: commit.message,
|
|
11488
11536
|
files: diffSummary.files.map((f) => f.file),
|
|
11489
11537
|
insertions: diffSummary.insertions,
|
|
11490
|
-
deletions: diffSummary.deletions
|
|
11538
|
+
deletions: diffSummary.deletions,
|
|
11539
|
+
branch
|
|
11491
11540
|
});
|
|
11492
11541
|
}
|
|
11493
11542
|
return commits;
|
|
@@ -11561,7 +11610,8 @@ var Analyzer = class {
|
|
|
11561
11610
|
return {
|
|
11562
11611
|
timeline: this.generateTimelineData(analysis.commits),
|
|
11563
11612
|
contributors: this.generateContributorData(analysis.contributors),
|
|
11564
|
-
heatmap: this.generateHeatmapData(analysis.files)
|
|
11613
|
+
heatmap: this.generateHeatmapData(analysis.files),
|
|
11614
|
+
branches: this.generateBranchData(analysis.branches || [])
|
|
11565
11615
|
};
|
|
11566
11616
|
}
|
|
11567
11617
|
generateTimelineData(commits) {
|
|
@@ -11597,6 +11647,13 @@ var Analyzer = class {
|
|
|
11597
11647
|
heat: f.changes / maxChanges
|
|
11598
11648
|
})).sort((a, b) => b.changes - a.changes).slice(0, 50);
|
|
11599
11649
|
}
|
|
11650
|
+
generateBranchData(branches) {
|
|
11651
|
+
return branches.map((b) => ({
|
|
11652
|
+
name: b.name,
|
|
11653
|
+
commits: b.commits,
|
|
11654
|
+
lastCommit: b.lastCommit.toISOString().split("T")[0]
|
|
11655
|
+
})).sort((a, b) => b.commits - a.commits);
|
|
11656
|
+
}
|
|
11600
11657
|
};
|
|
11601
11658
|
|
|
11602
11659
|
// src/visualizers/html-generator.ts
|
|
@@ -11624,6 +11681,12 @@ function generateHTML(data, repoName) {
|
|
|
11624
11681
|
padding: 3rem;
|
|
11625
11682
|
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
|
11626
11683
|
}
|
|
11684
|
+
.header {
|
|
11685
|
+
display: flex;
|
|
11686
|
+
justify-content: space-between;
|
|
11687
|
+
align-items: center;
|
|
11688
|
+
margin-bottom: 2rem;
|
|
11689
|
+
}
|
|
11627
11690
|
h1 {
|
|
11628
11691
|
font-size: 3rem;
|
|
11629
11692
|
margin-bottom: 0.5rem;
|
|
@@ -11635,7 +11698,33 @@ function generateHTML(data, repoName) {
|
|
|
11635
11698
|
.subtitle {
|
|
11636
11699
|
color: #666;
|
|
11637
11700
|
font-size: 1.2rem;
|
|
11638
|
-
margin-bottom:
|
|
11701
|
+
margin-bottom: 1rem;
|
|
11702
|
+
}
|
|
11703
|
+
.controls {
|
|
11704
|
+
display: flex;
|
|
11705
|
+
gap: 1rem;
|
|
11706
|
+
align-items: center;
|
|
11707
|
+
}
|
|
11708
|
+
.lang-switch {
|
|
11709
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
11710
|
+
color: white;
|
|
11711
|
+
border: none;
|
|
11712
|
+
padding: 0.5rem 1.5rem;
|
|
11713
|
+
border-radius: 8px;
|
|
11714
|
+
cursor: pointer;
|
|
11715
|
+
font-size: 1rem;
|
|
11716
|
+
transition: transform 0.2s;
|
|
11717
|
+
}
|
|
11718
|
+
.lang-switch:hover {
|
|
11719
|
+
transform: translateY(-2px);
|
|
11720
|
+
}
|
|
11721
|
+
.branch-selector {
|
|
11722
|
+
padding: 0.5rem 1rem;
|
|
11723
|
+
border: 2px solid #667eea;
|
|
11724
|
+
border-radius: 8px;
|
|
11725
|
+
font-size: 1rem;
|
|
11726
|
+
cursor: pointer;
|
|
11727
|
+
background: white;
|
|
11639
11728
|
}
|
|
11640
11729
|
.section {
|
|
11641
11730
|
margin-bottom: 4rem;
|
|
@@ -11664,6 +11753,7 @@ function generateHTML(data, repoName) {
|
|
|
11664
11753
|
pointer-events: none;
|
|
11665
11754
|
opacity: 0;
|
|
11666
11755
|
transition: opacity 0.3s;
|
|
11756
|
+
z-index: 1000;
|
|
11667
11757
|
}
|
|
11668
11758
|
.stats {
|
|
11669
11759
|
display: grid;
|
|
@@ -11687,40 +11777,106 @@ function generateHTML(data, repoName) {
|
|
|
11687
11777
|
font-size: 1rem;
|
|
11688
11778
|
opacity: 0.9;
|
|
11689
11779
|
}
|
|
11780
|
+
.branch-comparison {
|
|
11781
|
+
display: grid;
|
|
11782
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
11783
|
+
gap: 1rem;
|
|
11784
|
+
margin-bottom: 2rem;
|
|
11785
|
+
}
|
|
11786
|
+
.branch-card {
|
|
11787
|
+
background: white;
|
|
11788
|
+
border: 2px solid #667eea;
|
|
11789
|
+
border-radius: 12px;
|
|
11790
|
+
padding: 1.5rem;
|
|
11791
|
+
cursor: pointer;
|
|
11792
|
+
transition: all 0.3s;
|
|
11793
|
+
}
|
|
11794
|
+
.branch-card:hover {
|
|
11795
|
+
transform: translateY(-4px);
|
|
11796
|
+
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3);
|
|
11797
|
+
}
|
|
11798
|
+
.branch-card.active {
|
|
11799
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
11800
|
+
color: white;
|
|
11801
|
+
}
|
|
11802
|
+
.branch-name {
|
|
11803
|
+
font-size: 1.2rem;
|
|
11804
|
+
font-weight: bold;
|
|
11805
|
+
margin-bottom: 0.5rem;
|
|
11806
|
+
}
|
|
11807
|
+
.branch-info {
|
|
11808
|
+
font-size: 0.9rem;
|
|
11809
|
+
opacity: 0.8;
|
|
11810
|
+
}
|
|
11811
|
+
.hidden {
|
|
11812
|
+
display: none;
|
|
11813
|
+
}
|
|
11690
11814
|
</style>
|
|
11691
11815
|
</head>
|
|
11692
11816
|
<body>
|
|
11693
11817
|
<div class="container">
|
|
11694
|
-
<
|
|
11695
|
-
|
|
11818
|
+
<div class="header">
|
|
11819
|
+
<div>
|
|
11820
|
+
<h1>\u{1F4CA} GitViz</h1>
|
|
11821
|
+
<p class="subtitle" data-i18n="subtitle">Repository: ${repoName}</p>
|
|
11822
|
+
</div>
|
|
11823
|
+
<div class="controls">
|
|
11824
|
+
<button class="lang-switch" onclick="toggleLanguage()">
|
|
11825
|
+
<span id="lang-text">\u4E2D\u6587</span>
|
|
11826
|
+
</button>
|
|
11827
|
+
</div>
|
|
11828
|
+
</div>
|
|
11696
11829
|
|
|
11697
11830
|
<div class="stats">
|
|
11698
11831
|
<div class="stat-card">
|
|
11699
11832
|
<div class="stat-value">${data.timeline.reduce((sum, d) => sum + d.commits, 0)}</div>
|
|
11700
|
-
<div class="stat-label">Total Commits</div>
|
|
11833
|
+
<div class="stat-label" data-i18n="totalCommits">Total Commits</div>
|
|
11701
11834
|
</div>
|
|
11702
11835
|
<div class="stat-card">
|
|
11703
11836
|
<div class="stat-value">${data.contributors.length}</div>
|
|
11704
|
-
<div class="stat-label">Contributors</div>
|
|
11837
|
+
<div class="stat-label" data-i18n="contributors">Contributors</div>
|
|
11705
11838
|
</div>
|
|
11706
11839
|
<div class="stat-card">
|
|
11707
11840
|
<div class="stat-value">${data.heatmap.length}</div>
|
|
11708
|
-
<div class="stat-label">Files Changed</div>
|
|
11841
|
+
<div class="stat-label" data-i18n="filesChanged">Files Changed</div>
|
|
11842
|
+
</div>
|
|
11843
|
+
${data.branches && data.branches.length > 0 ? `
|
|
11844
|
+
<div class="stat-card">
|
|
11845
|
+
<div class="stat-value">${data.branches.length}</div>
|
|
11846
|
+
<div class="stat-label" data-i18n="branches">Branches</div>
|
|
11709
11847
|
</div>
|
|
11848
|
+
` : ""}
|
|
11710
11849
|
</div>
|
|
11711
11850
|
|
|
11851
|
+
${data.branches && data.branches.length > 1 ? `
|
|
11712
11852
|
<div class="section">
|
|
11713
|
-
<h2>\u{
|
|
11853
|
+
<h2 data-i18n="branchComparison">\u{1F33F} Branch Comparison</h2>
|
|
11854
|
+
<div class="branch-comparison" id="branchComparison">
|
|
11855
|
+
${data.branches.map((branch, idx) => `
|
|
11856
|
+
<div class="branch-card ${idx === 0 ? "active" : ""}" onclick="selectBranch('${branch.name}', this)">
|
|
11857
|
+
<div class="branch-name">${branch.name}</div>
|
|
11858
|
+
<div class="branch-info">
|
|
11859
|
+
<div><span data-i18n="commits">Commits</span>: ${branch.commits}</div>
|
|
11860
|
+
<div><span data-i18n="lastCommit">Last Commit</span>: ${branch.lastCommit}</div>
|
|
11861
|
+
</div>
|
|
11862
|
+
</div>
|
|
11863
|
+
`).join("")}
|
|
11864
|
+
</div>
|
|
11865
|
+
</div>
|
|
11866
|
+
` : ""}
|
|
11867
|
+
|
|
11868
|
+
<div class="section">
|
|
11869
|
+
<h2 data-i18n="commitTimeline">\u{1F4C8} Commit Timeline</h2>
|
|
11714
11870
|
<div class="chart" id="timeline"></div>
|
|
11715
11871
|
</div>
|
|
11716
11872
|
|
|
11717
11873
|
<div class="section">
|
|
11718
|
-
<h2>\u{1F465} Top Contributors</h2>
|
|
11874
|
+
<h2 data-i18n="topContributors">\u{1F465} Top Contributors</h2>
|
|
11719
11875
|
<div class="chart" id="contributors"></div>
|
|
11720
11876
|
</div>
|
|
11721
11877
|
|
|
11722
11878
|
<div class="section">
|
|
11723
|
-
<h2>\u{1F525} File Change Heatmap</h2>
|
|
11879
|
+
<h2 data-i18n="fileHeatmap">\u{1F525} File Change Heatmap</h2>
|
|
11724
11880
|
<div class="chart" id="heatmap"></div>
|
|
11725
11881
|
</div>
|
|
11726
11882
|
</div>
|
|
@@ -11729,6 +11885,74 @@ function generateHTML(data, repoName) {
|
|
|
11729
11885
|
|
|
11730
11886
|
<script>
|
|
11731
11887
|
const data = ${JSON.stringify(data)};
|
|
11888
|
+
let currentLang = 'en';
|
|
11889
|
+
let selectedBranch = null;
|
|
11890
|
+
|
|
11891
|
+
// \u56FD\u9645\u5316\u6587\u672C
|
|
11892
|
+
const i18n = {
|
|
11893
|
+
en: {
|
|
11894
|
+
subtitle: 'Repository: ${repoName}',
|
|
11895
|
+
totalCommits: 'Total Commits',
|
|
11896
|
+
contributors: 'Contributors',
|
|
11897
|
+
filesChanged: 'Files Changed',
|
|
11898
|
+
branches: 'Branches',
|
|
11899
|
+
branchComparison: '\u{1F33F} Branch Comparison',
|
|
11900
|
+
commits: 'Commits',
|
|
11901
|
+
lastCommit: 'Last Commit',
|
|
11902
|
+
commitTimeline: '\u{1F4C8} Commit Timeline',
|
|
11903
|
+
topContributors: '\u{1F465} Top Contributors',
|
|
11904
|
+
fileHeatmap: '\u{1F525} File Change Heatmap',
|
|
11905
|
+
date: 'Date',
|
|
11906
|
+
changes: 'Changes',
|
|
11907
|
+
insertions: 'Insertions',
|
|
11908
|
+
deletions: 'Deletions',
|
|
11909
|
+
lines: 'Lines'
|
|
11910
|
+
},
|
|
11911
|
+
zh: {
|
|
11912
|
+
subtitle: '\u4ED3\u5E93: ${repoName}',
|
|
11913
|
+
totalCommits: '\u603B\u63D0\u4EA4\u6570',
|
|
11914
|
+
contributors: '\u8D21\u732E\u8005',
|
|
11915
|
+
filesChanged: '\u6587\u4EF6\u53D8\u66F4',
|
|
11916
|
+
branches: '\u5206\u652F\u6570',
|
|
11917
|
+
branchComparison: '\u{1F33F} \u5206\u652F\u5BF9\u6BD4',
|
|
11918
|
+
commits: '\u63D0\u4EA4\u6570',
|
|
11919
|
+
lastCommit: '\u6700\u540E\u63D0\u4EA4',
|
|
11920
|
+
commitTimeline: '\u{1F4C8} \u63D0\u4EA4\u65F6\u95F4\u7EBF',
|
|
11921
|
+
topContributors: '\u{1F465} \u9876\u7EA7\u8D21\u732E\u8005',
|
|
11922
|
+
fileHeatmap: '\u{1F525} \u6587\u4EF6\u53D8\u66F4\u70ED\u529B\u56FE',
|
|
11923
|
+
date: '\u65E5\u671F',
|
|
11924
|
+
changes: '\u53D8\u66F4',
|
|
11925
|
+
insertions: '\u65B0\u589E',
|
|
11926
|
+
deletions: '\u5220\u9664',
|
|
11927
|
+
lines: '\u4EE3\u7801\u884C'
|
|
11928
|
+
}
|
|
11929
|
+
};
|
|
11930
|
+
|
|
11931
|
+
function toggleLanguage() {
|
|
11932
|
+
currentLang = currentLang === 'en' ? 'zh' : 'en';
|
|
11933
|
+
document.getElementById('lang-text').textContent = currentLang === 'en' ? '\u4E2D\u6587' : 'English';
|
|
11934
|
+
|
|
11935
|
+
// \u66F4\u65B0\u6240\u6709\u5E26 data-i18n \u5C5E\u6027\u7684\u5143\u7D20
|
|
11936
|
+
document.querySelectorAll('[data-i18n]').forEach(el => {
|
|
11937
|
+
const key = el.getAttribute('data-i18n');
|
|
11938
|
+
if (i18n[currentLang][key]) {
|
|
11939
|
+
el.textContent = i18n[currentLang][key];
|
|
11940
|
+
}
|
|
11941
|
+
});
|
|
11942
|
+
}
|
|
11943
|
+
|
|
11944
|
+
function selectBranch(branchName, element) {
|
|
11945
|
+
selectedBranch = branchName;
|
|
11946
|
+
|
|
11947
|
+
// \u66F4\u65B0\u9009\u4E2D\u72B6\u6001
|
|
11948
|
+
document.querySelectorAll('.branch-card').forEach(card => {
|
|
11949
|
+
card.classList.remove('active');
|
|
11950
|
+
});
|
|
11951
|
+
element.classList.add('active');
|
|
11952
|
+
|
|
11953
|
+
// \u8FD9\u91CC\u53EF\u4EE5\u6DFB\u52A0\u6839\u636E\u5206\u652F\u8FC7\u6EE4\u6570\u636E\u7684\u903B\u8F91
|
|
11954
|
+
console.log('Selected branch:', branchName);
|
|
11955
|
+
}
|
|
11732
11956
|
|
|
11733
11957
|
// Timeline Chart
|
|
11734
11958
|
{
|
|
@@ -11771,9 +11995,11 @@ function generateHTML(data, repoName) {
|
|
|
11771
11995
|
.attr("width", x.bandwidth())
|
|
11772
11996
|
.attr("height", d => height - y(d.commits))
|
|
11773
11997
|
.on("mouseover", function(event, d) {
|
|
11774
|
-
d3.select("#tooltip")
|
|
11998
|
+
const tooltip = d3.select("#tooltip");
|
|
11999
|
+
const lang = currentLang;
|
|
12000
|
+
tooltip
|
|
11775
12001
|
.style("opacity", 1)
|
|
11776
|
-
.html(\`<strong>\${d.date}</strong><br
|
|
12002
|
+
.html(\`<strong>\${d.date}</strong><br/>\${i18n[lang].commits}: \${d.commits}<br/>+\${d.insertions} -\${d.deletions}\`)
|
|
11777
12003
|
.style("left", (event.pageX + 10) + "px")
|
|
11778
12004
|
.style("top", (event.pageY - 10) + "px");
|
|
11779
12005
|
})
|
|
@@ -11826,9 +12052,11 @@ function generateHTML(data, repoName) {
|
|
|
11826
12052
|
.attr("width", x.bandwidth())
|
|
11827
12053
|
.attr("height", d => height - y(d.commits))
|
|
11828
12054
|
.on("mouseover", function(event, d) {
|
|
11829
|
-
d3.select("#tooltip")
|
|
12055
|
+
const tooltip = d3.select("#tooltip");
|
|
12056
|
+
const lang = currentLang;
|
|
12057
|
+
tooltip
|
|
11830
12058
|
.style("opacity", 1)
|
|
11831
|
-
.html(\`<strong>\${d.name}</strong><br
|
|
12059
|
+
.html(\`<strong>\${d.name}</strong><br/>\${i18n[lang].commits}: \${d.commits}<br/>\${i18n[lang].lines}: \${d.lines}\`)
|
|
11832
12060
|
.style("left", (event.pageX + 10) + "px")
|
|
11833
12061
|
.style("top", (event.pageY - 10) + "px");
|
|
11834
12062
|
})
|
|
@@ -11867,9 +12095,11 @@ function generateHTML(data, repoName) {
|
|
|
11867
12095
|
.attr("height", y.bandwidth())
|
|
11868
12096
|
.attr("fill", d => colorScale(d.heat))
|
|
11869
12097
|
.on("mouseover", function(event, d) {
|
|
11870
|
-
d3.select("#tooltip")
|
|
12098
|
+
const tooltip = d3.select("#tooltip");
|
|
12099
|
+
const lang = currentLang;
|
|
12100
|
+
tooltip
|
|
11871
12101
|
.style("opacity", 1)
|
|
11872
|
-
.html(\`<strong>\${d.file}</strong><br
|
|
12102
|
+
.html(\`<strong>\${d.file}</strong><br/>\${i18n[lang].changes}: \${d.changes}\`)
|
|
11873
12103
|
.style("left", (event.pageX + 10) + "px")
|
|
11874
12104
|
.style("top", (event.pageY - 10) + "px");
|
|
11875
12105
|
})
|
|
@@ -11918,9 +12148,12 @@ program2.command("analyze").description("Analyze a Git repository and generate v
|
|
|
11918
12148
|
spinner.fail(source_default.red("Error: No commits found"));
|
|
11919
12149
|
process.exit(1);
|
|
11920
12150
|
}
|
|
12151
|
+
spinner.text = "Getting branch information...";
|
|
12152
|
+
const branches = await parser4.getBranches();
|
|
11921
12153
|
spinner.text = `Analyzing ${commits.length} commits...`;
|
|
11922
12154
|
const analyzer = new Analyzer();
|
|
11923
12155
|
const analysis = analyzer.analyze(commits);
|
|
12156
|
+
analysis.branches = branches;
|
|
11924
12157
|
spinner.text = "Generating visualization data...";
|
|
11925
12158
|
const vizData = analyzer.generateVisualizationData(analysis);
|
|
11926
12159
|
spinner.text = "Creating HTML report...";
|
package/package.json
CHANGED
package/dist/cli/index.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
import ora from 'ora';
|
|
5
|
-
import { writeFileSync } from 'fs';
|
|
6
|
-
import { join } from 'path';
|
|
7
|
-
import { GitParser } from '../core/git-parser.js';
|
|
8
|
-
import { Analyzer } from '../core/analyzer.js';
|
|
9
|
-
import { generateHTML } from '../visualizers/html-generator.js';
|
|
10
|
-
const program = new Command();
|
|
11
|
-
program
|
|
12
|
-
.name('gitviz')
|
|
13
|
-
.description('🎨 Beautiful, interactive Git repository visualizations')
|
|
14
|
-
.version('1.0.0');
|
|
15
|
-
program
|
|
16
|
-
.command('analyze')
|
|
17
|
-
.description('Analyze a Git repository and generate visualizations')
|
|
18
|
-
.option('-p, --path <path>', 'Path to Git repository', '.')
|
|
19
|
-
.option('-o, --output <file>', 'Output HTML file', 'gitviz-report.html')
|
|
20
|
-
.option('-l, --limit <number>', 'Limit number of commits to analyze', '1000')
|
|
21
|
-
.action(async (options) => {
|
|
22
|
-
const spinner = ora('Initializing GitViz...').start();
|
|
23
|
-
try {
|
|
24
|
-
const parser = new GitParser(options.path);
|
|
25
|
-
spinner.text = 'Checking if directory is a Git repository...';
|
|
26
|
-
const isRepo = await parser.isGitRepository();
|
|
27
|
-
if (!isRepo) {
|
|
28
|
-
spinner.fail(chalk.red('Error: Not a Git repository'));
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
spinner.text = 'Parsing Git commits...';
|
|
32
|
-
const commits = await parser.parseCommits(parseInt(options.limit));
|
|
33
|
-
if (commits.length === 0) {
|
|
34
|
-
spinner.fail(chalk.red('Error: No commits found'));
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
spinner.text = `Analyzing ${commits.length} commits...`;
|
|
38
|
-
const analyzer = new Analyzer();
|
|
39
|
-
const analysis = analyzer.analyze(commits);
|
|
40
|
-
spinner.text = 'Generating visualization data...';
|
|
41
|
-
const vizData = analyzer.generateVisualizationData(analysis);
|
|
42
|
-
spinner.text = 'Creating HTML report...';
|
|
43
|
-
const repoName = options.path === '.' ? 'Current Repository' : options.path;
|
|
44
|
-
const html = generateHTML(vizData, repoName);
|
|
45
|
-
const outputPath = join(process.cwd(), options.output);
|
|
46
|
-
writeFileSync(outputPath, html, 'utf-8');
|
|
47
|
-
spinner.succeed(chalk.green('✨ Visualization generated successfully!'));
|
|
48
|
-
console.log('\n' + chalk.bold('📊 Repository Statistics:'));
|
|
49
|
-
console.log(chalk.cyan(` Total Commits: ${analysis.totalCommits}`));
|
|
50
|
-
console.log(chalk.cyan(` Contributors: ${analysis.totalContributors}`));
|
|
51
|
-
console.log(chalk.cyan(` Files Changed: ${analysis.files.size}`));
|
|
52
|
-
console.log(chalk.cyan(` Date Range: ${analysis.dateRange.start.toLocaleDateString()} - ${analysis.dateRange.end.toLocaleDateString()}`));
|
|
53
|
-
console.log('\n' + chalk.bold(`📁 Output: ${outputPath}`));
|
|
54
|
-
console.log(chalk.gray(`\nOpen the file in your browser to view the interactive visualization.\n`));
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
spinner.fail(chalk.red('Error: ' + error.message));
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
program
|
|
62
|
-
.command('quick')
|
|
63
|
-
.description('Quick analysis of current directory')
|
|
64
|
-
.action(async () => {
|
|
65
|
-
program.parse(['node', 'gitviz', 'analyze']);
|
|
66
|
-
});
|
|
67
|
-
program.parse();
|