@anxin233/gitviz 1.0.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/CHANGELOG.md ADDED
@@ -0,0 +1,49 @@
1
+ # Changelog
2
+
3
+ All notable changes to GitViz will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.0.0] - 2026-02-28
9
+
10
+ ### Added
11
+ - Initial release of GitViz
12
+ - Interactive commit timeline visualization
13
+ - Top contributors analysis with commit and line counts
14
+ - File change heatmap showing most frequently modified files
15
+ - CLI with `analyze` and `quick` commands
16
+ - HTML export functionality with embedded D3.js visualizations
17
+ - Support for custom output paths and commit limits
18
+ - Beautiful gradient UI with responsive design
19
+ - Hover tooltips for detailed information
20
+ - Repository statistics summary
21
+ - Cross-platform support (Windows, macOS, Linux)
22
+
23
+ ### Features
24
+ - Fast Git history parsing using simple-git
25
+ - Intelligent data aggregation and analysis
26
+ - Standalone HTML reports (no external dependencies)
27
+ - Configurable commit limits for large repositories
28
+ - Automatic repository detection
29
+ - Color-coded visualizations with smooth transitions
30
+
31
+ ### Technical
32
+ - Built with TypeScript for type safety
33
+ - Powered by Bun for optimal performance
34
+ - Uses D3.js v7 for data visualization
35
+ - Modular architecture for easy extension
36
+ - Clean separation of concerns (parser, analyzer, visualizer)
37
+
38
+ ## [Unreleased]
39
+
40
+ ### Planned
41
+ - Branch comparison feature
42
+ - Network graph visualization
43
+ - Export to PDF and PNG formats
44
+ - Custom theme support
45
+ - GitHub/GitLab API integration
46
+ - Real-time repository monitoring
47
+ - Plugin system for custom analyzers
48
+ - Multi-language support
49
+ - Performance optimizations for very large repositories
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GitViz Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,183 @@
1
+ # 🎨 GitViz
2
+
3
+ > Transform your Git history into stunning visual stories
4
+
5
+ English | [简体中文](README.zh-CN.md)
6
+
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.3-blue.svg)](https://www.typescriptlang.org/)
9
+ [![Bun](https://img.shields.io/badge/Bun-1.0-black.svg)](https://bun.sh/)
10
+ [![GitHub stars](https://img.shields.io/github/stars/anxin233/gitviz?style=social)](https://github.com/anxin233/gitviz)
11
+ [![GitHub issues](https://img.shields.io/github/issues/anxin233/gitviz)](https://github.com/anxin233/gitviz/issues)
12
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
13
+
14
+ GitViz is a powerful CLI tool that analyzes your Git repositories and generates beautiful, interactive visualizations. Understand your project's history, contributor patterns, and code evolution at a glance.
15
+
16
+ ![Demo](https://via.placeholder.com/800x400/667eea/ffffff?text=GitViz+Demo+Coming+Soon)
17
+
18
+ [📖 Quick Start](QUICKSTART.md) • [🎨 Demo](DEMO.md) • [📚 Examples](EXAMPLES.md) • [🤝 Contributing](CONTRIBUTING.md)
19
+
20
+ ## ✨ Features
21
+
22
+ - 📊 **Interactive Timeline** - Visualize commit activity over time
23
+ - 👥 **Contributor Analytics** - See who's contributing and how much
24
+ - 🔥 **File Heatmap** - Identify hotspots in your codebase
25
+ - 🚀 **Lightning Fast** - Powered by Bun for maximum performance
26
+ - 📦 **Zero Config** - Works out of the box with any Git repository
27
+ - 🎨 **Beautiful Output** - Export as standalone HTML files
28
+ - 💻 **Cross-Platform** - Works on macOS, Linux, and Windows
29
+
30
+ ## 🚀 Quick Start
31
+
32
+ ### Installation
33
+
34
+ ```bash
35
+ # Using npm
36
+ npm install -g @anxin233/gitviz
37
+
38
+ # Using Bun (recommended)
39
+ bun install -g @anxin233/gitviz
40
+
41
+ # Using yarn
42
+ yarn global add @anxin233/gitviz
43
+ ```
44
+
45
+ ### Usage
46
+
47
+ ```bash
48
+ # Analyze current repository
49
+ gitviz analyze
50
+
51
+ # Analyze specific repository
52
+ gitviz analyze --path /path/to/repo
53
+
54
+ # Customize output
55
+ gitviz analyze --output my-report.html --limit 500
56
+
57
+ # Quick analysis
58
+ gitviz quick
59
+ ```
60
+
61
+ ## 📊 What You Get
62
+
63
+ GitViz generates a comprehensive HTML report with:
64
+
65
+ ### 1. Commit Timeline
66
+ See your project's activity patterns over time. Identify busy periods, quiet phases, and development trends.
67
+
68
+ ### 2. Top Contributors
69
+ Understand who's driving your project forward. See commit counts and lines of code contributed.
70
+
71
+ ### 3. File Change Heatmap
72
+ Discover which files change most frequently. Perfect for identifying technical debt and refactoring opportunities.
73
+
74
+ ## 🎯 Use Cases
75
+
76
+ - **Project Health Checks** - Quickly assess repository activity
77
+ - **Team Analytics** - Understand contribution patterns
78
+ - **Documentation** - Add visual insights to your README
79
+ - **Code Reviews** - Identify frequently changed files
80
+ - **Onboarding** - Help new team members understand project history
81
+
82
+ ## 🛠️ CLI Options
83
+
84
+ ```
85
+ gitviz analyze [options]
86
+
87
+ Options:
88
+ -p, --path <path> Path to Git repository (default: ".")
89
+ -o, --output <file> Output HTML file (default: "gitviz-report.html")
90
+ -l, --limit <number> Limit number of commits (default: "1000")
91
+ -h, --help Display help information
92
+ ```
93
+
94
+ ## 📖 Examples
95
+
96
+ ### Basic Analysis
97
+ ```bash
98
+ gitviz analyze
99
+ ```
100
+
101
+ ### Analyze Large Repository
102
+ ```bash
103
+ gitviz analyze --limit 5000 --output full-history.html
104
+ ```
105
+
106
+ ### Analyze Multiple Projects
107
+ ```bash
108
+ gitviz analyze --path ~/projects/app1 --output app1-viz.html
109
+ gitviz analyze --path ~/projects/app2 --output app2-viz.html
110
+ ```
111
+
112
+ ## 🏗️ How It Works
113
+
114
+ 1. **Parse** - GitViz reads your Git history using `simple-git`
115
+ 2. **Analyze** - Processes commits, contributors, and file changes
116
+ 3. **Visualize** - Generates interactive D3.js charts
117
+ 4. **Export** - Creates a standalone HTML file you can share
118
+
119
+ ## 🤝 Contributing
120
+
121
+ Contributions are welcome! Here's how you can help:
122
+
123
+ 1. Fork the repository
124
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
125
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
126
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
127
+ 5. Open a Pull Request
128
+
129
+ ## 📝 Development
130
+
131
+ ```bash
132
+ # Clone the repository
133
+ git clone https://github.com/anxin233/gitviz.git
134
+ cd gitviz
135
+
136
+ # Install dependencies
137
+ bun install
138
+
139
+ # Run in development mode
140
+ bun run dev analyze
141
+
142
+ # Build
143
+ bun run build
144
+
145
+ # Type check
146
+ bun run type-check
147
+ ```
148
+
149
+ ## 🐛 Known Issues
150
+
151
+ - Very large repositories (>10k commits) may take a few seconds to process
152
+ - Binary files are included in change statistics
153
+
154
+ ## 🗺️ Roadmap
155
+
156
+ - [ ] Add more visualization types (network graphs, code frequency)
157
+ - [ ] Support for multiple branches comparison
158
+ - [ ] Export to PDF and PNG
159
+ - [ ] Real-time mode for live repositories
160
+ - [ ] Integration with GitHub/GitLab APIs
161
+ - [ ] Custom themes and color schemes
162
+ - [ ] Plugin system for custom analyzers
163
+
164
+ ## 📄 License
165
+
166
+ MIT © 2026 GitViz Contributors
167
+
168
+ ## 🌟 Show Your Support
169
+
170
+ If you find GitViz useful, please consider:
171
+ - ⭐ Starring the repository
172
+ - 🐦 Sharing on social media
173
+ - 🐛 Reporting bugs
174
+ - 💡 Suggesting new features
175
+
176
+ ## 📬 Contact
177
+
178
+ - Issues: [GitHub Issues](https://github.com/anxin233/gitviz/issues)
179
+ - Discussions: [GitHub Discussions](https://github.com/anxin233/gitviz/discussions)
180
+
181
+ ---
182
+
183
+ Made with ❤️ by developers, for developers
@@ -0,0 +1,183 @@
1
+ # 🎨 GitViz
2
+
3
+ > 将你的 Git 历史转换为令人惊艳的可视化故事
4
+
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.3-blue.svg)](https://www.typescriptlang.org/)
7
+ [![Bun](https://img.shields.io/badge/Bun-1.0-black.svg)](https://bun.sh/)
8
+ [![GitHub stars](https://img.shields.io/github/stars/anxin233/gitviz?style=social)](https://github.com/anxin233/gitviz)
9
+ [![GitHub issues](https://img.shields.io/github/issues/anxin233/gitviz)](https://github.com/anxin233/gitviz/issues)
10
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
11
+
12
+ [English](README.md) | 简体中文
13
+
14
+ GitViz 是一个强大的命令行工具,可以分析你的 Git 仓库并生成精美的交互式可视化图表。一目了然地理解项目历史、贡献者模式和代码演化过程。
15
+
16
+ ![演示](https://via.placeholder.com/800x400/667eea/ffffff?text=GitViz+Demo+Coming+Soon)
17
+
18
+ [📖 快速开始](QUICKSTART.md) • [🎨 演示](DEMO.md) • [📚 示例](EXAMPLES.md) • [🤝 贡献指南](CONTRIBUTING.md)
19
+
20
+ ## ✨ 特性
21
+
22
+ - 📊 **交互式时间线** - 可视化提交活动随时间的变化
23
+ - 👥 **贡献者分析** - 查看谁在贡献以及贡献了多少
24
+ - 🔥 **文件热力图** - 识别代码库中的热点区域
25
+ - 🚀 **极速性能** - 基于 Bun 实现最佳性能
26
+ - 📦 **零配置** - 开箱即用,适用于任何 Git 仓库
27
+ - 🎨 **精美输出** - 导出为独立的 HTML 文件
28
+ - 💻 **跨平台** - 支持 macOS、Linux 和 Windows
29
+
30
+ ## 🚀 快速开始
31
+
32
+ ### 安装
33
+
34
+ ```bash
35
+ # 使用 npm
36
+ npm install -g @anxin233/gitviz
37
+
38
+ # 使用 Bun(推荐)
39
+ bun install -g @anxin233/gitviz
40
+
41
+ # 使用 yarn
42
+ yarn global add @anxin233/gitviz
43
+ ```
44
+
45
+ ### 使用方法
46
+
47
+ ```bash
48
+ # 分析当前仓库
49
+ gitviz analyze
50
+
51
+ # 分析指定仓库
52
+ gitviz analyze --path /path/to/repo
53
+
54
+ # 自定义输出
55
+ gitviz analyze --output my-report.html --limit 500
56
+
57
+ # 快速分析
58
+ gitviz quick
59
+ ```
60
+
61
+ ## 📊 你将获得什么
62
+
63
+ GitViz 会生成一份全面的 HTML 报告,包含:
64
+
65
+ ### 1. 提交时间线
66
+ 查看项目随时间的活动模式。识别繁忙期、平静期和开发趋势。
67
+
68
+ ### 2. 顶级贡献者
69
+ 了解谁在推动项目前进。查看提交次数和贡献的代码行数。
70
+
71
+ ### 3. 文件变更热力图
72
+ 发现哪些文件变更最频繁。非常适合识别技术债务和重构机会。
73
+
74
+ ## 🎯 使用场景
75
+
76
+ - **项目健康检查** - 快速评估仓库活跃度
77
+ - **团队分析** - 理解贡献模式
78
+ - **文档编写** - 为 README 添加可视化洞察
79
+ - **代码审查** - 识别频繁变更的文件
80
+ - **新人入职** - 帮助新团队成员理解项目历史
81
+
82
+ ## 🛠️ 命令行选项
83
+
84
+ ```
85
+ gitviz analyze [选项]
86
+
87
+ 选项:
88
+ -p, --path <path> Git 仓库路径(默认: ".")
89
+ -o, --output <file> 输出 HTML 文件(默认: "gitviz-report.html")
90
+ -l, --limit <number> 限制分析的提交数量(默认: "1000")
91
+ -h, --help 显示帮助信息
92
+ ```
93
+
94
+ ## 📖 示例
95
+
96
+ ### 基础分析
97
+ ```bash
98
+ gitviz analyze
99
+ ```
100
+
101
+ ### 分析大型仓库
102
+ ```bash
103
+ gitviz analyze --limit 5000 --output full-history.html
104
+ ```
105
+
106
+ ### 分析多个项目
107
+ ```bash
108
+ gitviz analyze --path ~/projects/app1 --output app1-viz.html
109
+ gitviz analyze --path ~/projects/app2 --output app2-viz.html
110
+ ```
111
+
112
+ ## 🏗️ 工作原理
113
+
114
+ 1. **解析** - GitViz 使用 `simple-git` 读取 Git 历史
115
+ 2. **分析** - 处理提交、贡献者和文件变更
116
+ 3. **可视化** - 生成交互式 D3.js 图表
117
+ 4. **导出** - 创建可分享的独立 HTML 文件
118
+
119
+ ## 🤝 贡献
120
+
121
+ 欢迎贡献!你可以这样帮助我们:
122
+
123
+ 1. Fork 本仓库
124
+ 2. 创建特性分支(`git checkout -b feature/amazing-feature`)
125
+ 3. 提交你的更改(`git commit -m '添加某个很棒的特性'`)
126
+ 4. 推送到分支(`git push origin feature/amazing-feature`)
127
+ 5. 开启一个 Pull Request
128
+
129
+ ## 📝 开发
130
+
131
+ ```bash
132
+ # 克隆仓库
133
+ git clone https://github.com/anxin233/gitviz.git
134
+ cd gitviz
135
+
136
+ # 安装依赖
137
+ bun install
138
+
139
+ # 开发模式运行
140
+ bun run dev analyze
141
+
142
+ # 构建
143
+ bun run build
144
+
145
+ # 类型检查
146
+ bun run type-check
147
+ ```
148
+
149
+ ## 🐛 已知问题
150
+
151
+ - 超大型仓库(>10k 提交)可能需要几秒钟处理
152
+ - 二进制文件会被包含在变更统计中
153
+
154
+ ## 🗺️ 路线图
155
+
156
+ - [ ] 添加更多可视化类型(网络图、代码频率)
157
+ - [ ] 支持多分支对比
158
+ - [ ] 导出为 PDF 和 PNG 格式
159
+ - [ ] 实时模式监控活跃仓库
160
+ - [ ] 集成 GitHub/GitLab API
161
+ - [ ] 自定义主题和配色方案
162
+ - [ ] 插件系统支持自定义分析器
163
+
164
+ ## 📄 许可证
165
+
166
+ MIT © 2026 GitViz 贡献者
167
+
168
+ ## 🌟 支持我们
169
+
170
+ 如果你觉得 GitViz 有用,请考虑:
171
+ - ⭐ 给仓库点个星
172
+ - 🐦 在社交媒体上分享
173
+ - 🐛 报告 bug
174
+ - 💡 提出新功能建议
175
+
176
+ ## 📬 联系方式
177
+
178
+ - Issues: [GitHub Issues](https://github.com/anxin233/gitviz/issues)
179
+ - Discussions: [GitHub Discussions](https://github.com/anxin233/gitviz/discussions)
180
+
181
+ ---
182
+
183
+ 用 ❤️ 由开发者为开发者打造
@@ -0,0 +1,67 @@
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();
package/dist/cli.js ADDED
@@ -0,0 +1,67 @@
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();
@@ -0,0 +1,99 @@
1
+ export class Analyzer {
2
+ analyze(commits) {
3
+ const contributors = new Map();
4
+ const files = new Map();
5
+ for (const commit of commits) {
6
+ const key = commit.email || commit.author;
7
+ if (!contributors.has(key)) {
8
+ contributors.set(key, {
9
+ name: commit.author,
10
+ email: commit.email,
11
+ commits: 0,
12
+ insertions: 0,
13
+ deletions: 0,
14
+ firstCommit: commit.date,
15
+ lastCommit: commit.date
16
+ });
17
+ }
18
+ const contributor = contributors.get(key);
19
+ contributor.commits++;
20
+ contributor.insertions += commit.insertions;
21
+ contributor.deletions += commit.deletions;
22
+ contributor.lastCommit = commit.date;
23
+ for (const file of commit.files) {
24
+ if (!files.has(file)) {
25
+ files.set(file, {
26
+ path: file,
27
+ changes: 0,
28
+ lastModified: commit.date,
29
+ contributors: new Set()
30
+ });
31
+ }
32
+ const fileChange = files.get(file);
33
+ fileChange.changes++;
34
+ fileChange.lastModified = commit.date;
35
+ fileChange.contributors.add(commit.author);
36
+ }
37
+ }
38
+ const dates = commits.map(c => c.date);
39
+ return {
40
+ commits,
41
+ contributors,
42
+ files,
43
+ totalCommits: commits.length,
44
+ totalContributors: contributors.size,
45
+ dateRange: {
46
+ start: new Date(Math.min(...dates.map(d => d.getTime()))),
47
+ end: new Date(Math.max(...dates.map(d => d.getTime())))
48
+ }
49
+ };
50
+ }
51
+ generateVisualizationData(analysis) {
52
+ return {
53
+ timeline: this.generateTimelineData(analysis.commits),
54
+ contributors: this.generateContributorData(analysis.contributors),
55
+ heatmap: this.generateHeatmapData(analysis.files)
56
+ };
57
+ }
58
+ generateTimelineData(commits) {
59
+ const dailyStats = new Map();
60
+ for (const commit of commits) {
61
+ const dateKey = commit.date.toISOString().split('T')[0];
62
+ if (!dailyStats.has(dateKey)) {
63
+ dailyStats.set(dateKey, { commits: 0, insertions: 0, deletions: 0 });
64
+ }
65
+ const stats = dailyStats.get(dateKey);
66
+ stats.commits++;
67
+ stats.insertions += commit.insertions;
68
+ stats.deletions += commit.deletions;
69
+ }
70
+ return Array.from(dailyStats.entries())
71
+ .map(([date, stats]) => ({
72
+ date,
73
+ ...stats
74
+ }))
75
+ .sort((a, b) => a.date.localeCompare(b.date));
76
+ }
77
+ generateContributorData(contributors) {
78
+ return Array.from(contributors.values())
79
+ .map(c => ({
80
+ name: c.name,
81
+ commits: c.commits,
82
+ lines: c.insertions + c.deletions
83
+ }))
84
+ .sort((a, b) => b.commits - a.commits)
85
+ .slice(0, 20);
86
+ }
87
+ generateHeatmapData(files) {
88
+ const fileArray = Array.from(files.values());
89
+ const maxChanges = Math.max(...fileArray.map(f => f.changes));
90
+ return fileArray
91
+ .map(f => ({
92
+ file: f.path,
93
+ changes: f.changes,
94
+ heat: f.changes / maxChanges
95
+ }))
96
+ .sort((a, b) => b.changes - a.changes)
97
+ .slice(0, 50);
98
+ }
99
+ }
@@ -0,0 +1,53 @@
1
+ import simpleGit from 'simple-git';
2
+ export class GitParser {
3
+ git;
4
+ repoPath;
5
+ constructor(repoPath = '.') {
6
+ this.repoPath = repoPath;
7
+ this.git = simpleGit(repoPath);
8
+ }
9
+ async isGitRepository() {
10
+ try {
11
+ await this.git.status();
12
+ return true;
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ }
18
+ async parseCommits(limit = 1000) {
19
+ const log = await this.git.log({
20
+ maxCount: limit,
21
+ '--numstat': null,
22
+ '--pretty': 'format:%H|%an|%ae|%ai|%s'
23
+ });
24
+ const commits = [];
25
+ for (const commit of log.all) {
26
+ const diffSummary = await this.git.diffSummary([`${commit.hash}^`, commit.hash]);
27
+ commits.push({
28
+ hash: commit.hash,
29
+ author: commit.author_name || 'Unknown',
30
+ email: commit.author_email || '',
31
+ date: new Date(commit.date),
32
+ message: commit.message,
33
+ files: diffSummary.files.map(f => f.file),
34
+ insertions: diffSummary.insertions,
35
+ deletions: diffSummary.deletions
36
+ });
37
+ }
38
+ return commits;
39
+ }
40
+ async getBranchName() {
41
+ const branch = await this.git.branch();
42
+ return branch.current;
43
+ }
44
+ async getRemoteUrl() {
45
+ try {
46
+ const remotes = await this.git.getRemotes(true);
47
+ return remotes[0]?.refs?.fetch || null;
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
53
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,292 @@
1
+ export function generateHTML(data, repoName) {
2
+ return `<!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>GitViz - ${repoName}</title>
8
+ <script src="https://d3js.org/d3.v7.min.js"></script>
9
+ <style>
10
+ * { margin: 0; padding: 0; box-sizing: border-box; }
11
+ body {
12
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
13
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
14
+ color: #333;
15
+ padding: 2rem;
16
+ }
17
+ .container {
18
+ max-width: 1400px;
19
+ margin: 0 auto;
20
+ background: white;
21
+ border-radius: 20px;
22
+ padding: 3rem;
23
+ box-shadow: 0 20px 60px rgba(0,0,0,0.3);
24
+ }
25
+ h1 {
26
+ font-size: 3rem;
27
+ margin-bottom: 0.5rem;
28
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
29
+ -webkit-background-clip: text;
30
+ -webkit-text-fill-color: transparent;
31
+ background-clip: text;
32
+ }
33
+ .subtitle {
34
+ color: #666;
35
+ font-size: 1.2rem;
36
+ margin-bottom: 3rem;
37
+ }
38
+ .section {
39
+ margin-bottom: 4rem;
40
+ }
41
+ .section h2 {
42
+ font-size: 1.8rem;
43
+ margin-bottom: 1.5rem;
44
+ color: #333;
45
+ }
46
+ .chart {
47
+ background: #f8f9fa;
48
+ border-radius: 12px;
49
+ padding: 2rem;
50
+ }
51
+ .bar { fill: #667eea; transition: fill 0.3s; }
52
+ .bar:hover { fill: #764ba2; }
53
+ .axis { font-size: 12px; }
54
+ .axis path, .axis line { stroke: #ddd; }
55
+ .tooltip {
56
+ position: absolute;
57
+ background: rgba(0,0,0,0.8);
58
+ color: white;
59
+ padding: 8px 12px;
60
+ border-radius: 6px;
61
+ font-size: 14px;
62
+ pointer-events: none;
63
+ opacity: 0;
64
+ transition: opacity 0.3s;
65
+ }
66
+ .stats {
67
+ display: grid;
68
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
69
+ gap: 1.5rem;
70
+ margin-bottom: 3rem;
71
+ }
72
+ .stat-card {
73
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
74
+ color: white;
75
+ padding: 1.5rem;
76
+ border-radius: 12px;
77
+ text-align: center;
78
+ }
79
+ .stat-value {
80
+ font-size: 2.5rem;
81
+ font-weight: bold;
82
+ margin-bottom: 0.5rem;
83
+ }
84
+ .stat-label {
85
+ font-size: 1rem;
86
+ opacity: 0.9;
87
+ }
88
+ </style>
89
+ </head>
90
+ <body>
91
+ <div class="container">
92
+ <h1>📊 GitViz</h1>
93
+ <p class="subtitle">Repository: ${repoName}</p>
94
+
95
+ <div class="stats">
96
+ <div class="stat-card">
97
+ <div class="stat-value">${data.timeline.reduce((sum, d) => sum + d.commits, 0)}</div>
98
+ <div class="stat-label">Total Commits</div>
99
+ </div>
100
+ <div class="stat-card">
101
+ <div class="stat-value">${data.contributors.length}</div>
102
+ <div class="stat-label">Contributors</div>
103
+ </div>
104
+ <div class="stat-card">
105
+ <div class="stat-value">${data.heatmap.length}</div>
106
+ <div class="stat-label">Files Changed</div>
107
+ </div>
108
+ </div>
109
+
110
+ <div class="section">
111
+ <h2>📈 Commit Timeline</h2>
112
+ <div class="chart" id="timeline"></div>
113
+ </div>
114
+
115
+ <div class="section">
116
+ <h2>👥 Top Contributors</h2>
117
+ <div class="chart" id="contributors"></div>
118
+ </div>
119
+
120
+ <div class="section">
121
+ <h2>🔥 File Change Heatmap</h2>
122
+ <div class="chart" id="heatmap"></div>
123
+ </div>
124
+ </div>
125
+
126
+ <div class="tooltip" id="tooltip"></div>
127
+
128
+ <script>
129
+ const data = ${JSON.stringify(data)};
130
+
131
+ // Timeline Chart
132
+ {
133
+ const margin = {top: 20, right: 30, bottom: 40, left: 50};
134
+ const width = 1200 - margin.left - margin.right;
135
+ const height = 300 - margin.top - margin.bottom;
136
+
137
+ const svg = d3.select("#timeline")
138
+ .append("svg")
139
+ .attr("width", width + margin.left + margin.right)
140
+ .attr("height", height + margin.top + margin.bottom)
141
+ .append("g")
142
+ .attr("transform", \`translate(\${margin.left},\${margin.top})\`);
143
+
144
+ const x = d3.scaleBand()
145
+ .domain(data.timeline.map(d => d.date))
146
+ .range([0, width])
147
+ .padding(0.1);
148
+
149
+ const y = d3.scaleLinear()
150
+ .domain([0, d3.max(data.timeline, d => d.commits)])
151
+ .range([height, 0]);
152
+
153
+ svg.append("g")
154
+ .attr("class", "axis")
155
+ .attr("transform", \`translate(0,\${height})\`)
156
+ .call(d3.axisBottom(x).tickValues(x.domain().filter((d, i) => i % Math.ceil(data.timeline.length / 10) === 0)));
157
+
158
+ svg.append("g")
159
+ .attr("class", "axis")
160
+ .call(d3.axisLeft(y));
161
+
162
+ svg.selectAll(".bar")
163
+ .data(data.timeline)
164
+ .enter()
165
+ .append("rect")
166
+ .attr("class", "bar")
167
+ .attr("x", d => x(d.date))
168
+ .attr("y", d => y(d.commits))
169
+ .attr("width", x.bandwidth())
170
+ .attr("height", d => height - y(d.commits))
171
+ .on("mouseover", function(event, d) {
172
+ d3.select("#tooltip")
173
+ .style("opacity", 1)
174
+ .html(\`<strong>\${d.date}</strong><br/>Commits: \${d.commits}<br/>+\${d.insertions} -\${d.deletions}\`)
175
+ .style("left", (event.pageX + 10) + "px")
176
+ .style("top", (event.pageY - 10) + "px");
177
+ })
178
+ .on("mouseout", function() {
179
+ d3.select("#tooltip").style("opacity", 0);
180
+ });
181
+ }
182
+
183
+ // Contributors Chart
184
+ {
185
+ const margin = {top: 20, right: 30, bottom: 100, left: 50};
186
+ const width = 1200 - margin.left - margin.right;
187
+ const height = 400 - margin.top - margin.bottom;
188
+
189
+ const svg = d3.select("#contributors")
190
+ .append("svg")
191
+ .attr("width", width + margin.left + margin.right)
192
+ .attr("height", height + margin.top + margin.bottom)
193
+ .append("g")
194
+ .attr("transform", \`translate(\${margin.left},\${margin.top})\`);
195
+
196
+ const x = d3.scaleBand()
197
+ .domain(data.contributors.map(d => d.name))
198
+ .range([0, width])
199
+ .padding(0.2);
200
+
201
+ const y = d3.scaleLinear()
202
+ .domain([0, d3.max(data.contributors, d => d.commits)])
203
+ .range([height, 0]);
204
+
205
+ svg.append("g")
206
+ .attr("class", "axis")
207
+ .attr("transform", \`translate(0,\${height})\`)
208
+ .call(d3.axisBottom(x))
209
+ .selectAll("text")
210
+ .attr("transform", "rotate(-45)")
211
+ .style("text-anchor", "end");
212
+
213
+ svg.append("g")
214
+ .attr("class", "axis")
215
+ .call(d3.axisLeft(y));
216
+
217
+ svg.selectAll(".bar")
218
+ .data(data.contributors)
219
+ .enter()
220
+ .append("rect")
221
+ .attr("class", "bar")
222
+ .attr("x", d => x(d.name))
223
+ .attr("y", d => y(d.commits))
224
+ .attr("width", x.bandwidth())
225
+ .attr("height", d => height - y(d.commits))
226
+ .on("mouseover", function(event, d) {
227
+ d3.select("#tooltip")
228
+ .style("opacity", 1)
229
+ .html(\`<strong>\${d.name}</strong><br/>Commits: \${d.commits}<br/>Lines: \${d.lines}\`)
230
+ .style("left", (event.pageX + 10) + "px")
231
+ .style("top", (event.pageY - 10) + "px");
232
+ })
233
+ .on("mouseout", function() {
234
+ d3.select("#tooltip").style("opacity", 0);
235
+ });
236
+ }
237
+
238
+ // Heatmap
239
+ {
240
+ const margin = {top: 20, right: 30, bottom: 20, left: 300};
241
+ const width = 1200 - margin.left - margin.right;
242
+ const height = Math.min(800, data.heatmap.length * 20);
243
+
244
+ const svg = d3.select("#heatmap")
245
+ .append("svg")
246
+ .attr("width", width + margin.left + margin.right)
247
+ .attr("height", height + margin.top + margin.bottom)
248
+ .append("g")
249
+ .attr("transform", \`translate(\${margin.left},\${margin.top})\`);
250
+
251
+ const y = d3.scaleBand()
252
+ .domain(data.heatmap.map(d => d.file))
253
+ .range([0, height])
254
+ .padding(0.1);
255
+
256
+ const colorScale = d3.scaleSequential(d3.interpolateReds)
257
+ .domain([0, 1]);
258
+
259
+ svg.selectAll("rect")
260
+ .data(data.heatmap)
261
+ .enter()
262
+ .append("rect")
263
+ .attr("y", d => y(d.file))
264
+ .attr("width", width)
265
+ .attr("height", y.bandwidth())
266
+ .attr("fill", d => colorScale(d.heat))
267
+ .on("mouseover", function(event, d) {
268
+ d3.select("#tooltip")
269
+ .style("opacity", 1)
270
+ .html(\`<strong>\${d.file}</strong><br/>Changes: \${d.changes}\`)
271
+ .style("left", (event.pageX + 10) + "px")
272
+ .style("top", (event.pageY - 10) + "px");
273
+ })
274
+ .on("mouseout", function() {
275
+ d3.select("#tooltip").style("opacity", 0);
276
+ });
277
+
278
+ svg.selectAll("text")
279
+ .data(data.heatmap)
280
+ .enter()
281
+ .append("text")
282
+ .attr("x", -10)
283
+ .attr("y", d => y(d.file) + y.bandwidth() / 2)
284
+ .attr("dy", "0.35em")
285
+ .attr("text-anchor", "end")
286
+ .attr("font-size", "12px")
287
+ .text(d => d.file);
288
+ }
289
+ </script>
290
+ </body>
291
+ </html>`;
292
+ }
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@anxin233/gitviz",
3
+ "version": "1.0.0",
4
+ "description": "🎨 Beautiful, interactive Git repository visualizations - Transform your Git history into stunning visual stories",
5
+ "type": "module",
6
+ "bin": {
7
+ "gitviz": "./dist/cli.js"
8
+ },
9
+ "scripts": {
10
+ "dev": "node --loader ts-node/esm src/cli/index.ts",
11
+ "build": "tsc && node scripts/bundle.js",
12
+ "build:bun": "bun build src/cli/index.ts --outfile dist/cli.js --target node --minify",
13
+ "build:web": "cd src/web && vite build",
14
+ "preview": "cd src/web && vite preview",
15
+ "type-check": "tsc --noEmit",
16
+ "prepublishOnly": "npm run build"
17
+ },
18
+ "keywords": [
19
+ "git",
20
+ "visualization",
21
+ "analytics",
22
+ "cli",
23
+ "developer-tools",
24
+ "git-history",
25
+ "d3",
26
+ "charts"
27
+ ],
28
+ "author": "anxin233",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/anxin233/gitviz.git"
33
+ },
34
+ "homepage": "https://github.com/anxin233/gitviz#readme",
35
+ "bugs": {
36
+ "url": "https://github.com/anxin233/gitviz/issues"
37
+ },
38
+ "dependencies": {
39
+ "commander": "^12.0.0",
40
+ "simple-git": "^3.22.0",
41
+ "chalk": "^5.3.0",
42
+ "ora": "^8.0.1",
43
+ "d3": "^7.9.0"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^20.11.0",
47
+ "@types/d3": "^7.4.3",
48
+ "typescript": "^5.3.3",
49
+ "vite": "^5.0.12",
50
+ "@vitejs/plugin-react": "^4.2.1",
51
+ "react": "^18.2.0",
52
+ "react-dom": "^18.2.0",
53
+ "@types/react": "^18.2.48",
54
+ "@types/react-dom": "^18.2.18"
55
+ },
56
+ "engines": {
57
+ "node": ">=18.0.0"
58
+ }
59
+ }
@@ -0,0 +1,28 @@
1
+ import { readFileSync, writeFileSync, mkdirSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const rootDir = join(__dirname, '..');
7
+
8
+ // 创建 dist 目录
9
+ mkdirSync(join(rootDir, 'dist'), { recursive: true });
10
+
11
+ // 读取编译后的文件
12
+ const cliPath = join(rootDir, 'dist', 'cli', 'index.js');
13
+ const outputPath = join(rootDir, 'dist', 'cli.js');
14
+
15
+ try {
16
+ const content = readFileSync(cliPath, 'utf-8');
17
+
18
+ // 添加 shebang
19
+ const finalContent = '#!/usr/bin/env node\n' + content;
20
+
21
+ // 写入最终文件
22
+ writeFileSync(outputPath, finalContent, 'utf-8');
23
+
24
+ console.log('✅ Build successful: dist/cli.js');
25
+ } catch (error) {
26
+ console.error('❌ Build failed:', error.message);
27
+ process.exit(1);
28
+ }
@@ -0,0 +1,47 @@
1
+ import { readFileSync, writeFileSync, readdirSync, statSync, mkdirSync, cpSync } from 'fs';
2
+ import { join, dirname, relative } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const rootDir = join(__dirname, '..');
7
+ const distDir = join(rootDir, 'dist');
8
+
9
+ console.log('📦 Bundling CLI...');
10
+
11
+ // 读取所有编译后的文件并合并
12
+ function getAllFiles(dir, fileList = []) {
13
+ const files = readdirSync(dir);
14
+ files.forEach(file => {
15
+ const filePath = join(dir, file);
16
+ if (statSync(filePath).isDirectory()) {
17
+ getAllFiles(filePath, fileList);
18
+ } else if (file.endsWith('.js')) {
19
+ fileList.push(filePath);
20
+ }
21
+ });
22
+ return fileList;
23
+ }
24
+
25
+ try {
26
+ // 简单方案:直接复制 cli/index.js 作为入口
27
+ const cliIndexPath = join(distDir, 'cli', 'index.js');
28
+ const outputPath = join(distDir, 'cli.js');
29
+
30
+ if (!statSync(cliIndexPath).isFile()) {
31
+ throw new Error('CLI index.js not found');
32
+ }
33
+
34
+ let content = readFileSync(cliIndexPath, 'utf-8');
35
+
36
+ // 确保有 shebang
37
+ if (!content.startsWith('#!')) {
38
+ content = '#!/usr/bin/env node\n' + content;
39
+ }
40
+
41
+ writeFileSync(outputPath, content, 'utf-8');
42
+
43
+ console.log('✅ Bundle created: dist/cli.js');
44
+ } catch (error) {
45
+ console.error('❌ Bundle failed:', error.message);
46
+ process.exit(1);
47
+ }