git-trace 0.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.
Files changed (48) hide show
  1. package/.tracerc.example +38 -0
  2. package/README.md +136 -0
  3. package/bun.lock +511 -0
  4. package/bunchee.config.ts +11 -0
  5. package/cli/index.ts +251 -0
  6. package/cli/parser.ts +76 -0
  7. package/cli/tsconfig.json +6 -0
  8. package/dist/cli/index.d.ts +1 -0
  9. package/dist/cli/index.js +858 -0
  10. package/dist/config.cjs +66 -0
  11. package/dist/config.d.ts +15 -0
  12. package/dist/config.js +63 -0
  13. package/dist/highlight/index.cjs +770 -0
  14. package/dist/highlight/index.d.ts +26 -0
  15. package/dist/highlight/index.js +766 -0
  16. package/dist/index.cjs +849 -0
  17. package/dist/index.d.ts +52 -0
  18. package/dist/index.js +845 -0
  19. package/examples/demo/App.tsx +78 -0
  20. package/examples/demo/index.html +12 -0
  21. package/examples/demo/main.tsx +10 -0
  22. package/examples/demo/mockData.ts +170 -0
  23. package/examples/demo/styles.css +103 -0
  24. package/examples/demo/tsconfig.json +21 -0
  25. package/examples/demo/tsconfig.node.json +10 -0
  26. package/examples/demo/vite.config.ts +20 -0
  27. package/package.json +58 -0
  28. package/src/Trace.tsx +717 -0
  29. package/src/cache.ts +118 -0
  30. package/src/config.ts +51 -0
  31. package/src/entries/config.ts +7 -0
  32. package/src/entries/gitea.ts +4 -0
  33. package/src/entries/github.ts +5 -0
  34. package/src/entries/gitlab.ts +4 -0
  35. package/src/gitea.ts +58 -0
  36. package/src/github.ts +100 -0
  37. package/src/gitlab.ts +65 -0
  38. package/src/highlight/highlight.ts +119 -0
  39. package/src/highlight/index.ts +4 -0
  40. package/src/host.ts +32 -0
  41. package/src/index.ts +6 -0
  42. package/src/patterns.ts +6 -0
  43. package/src/shared.ts +108 -0
  44. package/src/themes.ts +98 -0
  45. package/src/types.ts +72 -0
  46. package/test/e2e.html +424 -0
  47. package/tsconfig.json +18 -0
  48. package/vercel.json +4 -0
@@ -0,0 +1,78 @@
1
+ import { useState } from 'react'
2
+ import { Trace } from 'git-trace'
3
+ import { commits } from './mockData'
4
+ import type { Theme, FilterOptions } from 'git-trace'
5
+
6
+ const THEMES: { value: Theme; label: string }[] = [
7
+ { value: 'dark', label: 'Dark' },
8
+ { value: 'midnight', label: 'Midnight' },
9
+ { value: 'cyber', label: 'Cyber' },
10
+ { value: 'forest', label: 'Forest' },
11
+ { value: 'sunset', label: 'Sunset' },
12
+ { value: 'light', label: 'Light' }
13
+ ]
14
+
15
+ export function App() {
16
+ const [theme, setTheme] = useState<Theme>('dark')
17
+ const [autoPlay, setAutoPlay] = useState(true)
18
+ const [interval, setInterval] = useState(1500)
19
+ const [filterable, setFilterable] = useState(true)
20
+ const [filter, setFilter] = useState<FilterOptions>({})
21
+
22
+ return (
23
+ <div className="app">
24
+ <header className="app-header">
25
+ <h1 className="app-title">
26
+ <span>Trace</span> Git History Visualizer
27
+ </h1>
28
+ <div className="app-controls">
29
+ <button
30
+ className="app-select"
31
+ onClick={() => setFilterable(!filterable)}
32
+ >
33
+ {filterable ? 'Filter: ON' : 'Filter: OFF'}
34
+ </button>
35
+ <select
36
+ className="app-select"
37
+ value={theme}
38
+ onChange={(e) => setTheme(e.target.value as Theme)}
39
+ >
40
+ {THEMES.map((t) => (
41
+ <option key={t.value} value={t.value}>
42
+ {t.label}
43
+ </option>
44
+ ))}
45
+ </select>
46
+ <button
47
+ className="app-select"
48
+ onClick={() => setAutoPlay(!autoPlay)}
49
+ >
50
+ {autoPlay ? 'Auto-play: ON' : 'Auto-play: OFF'}
51
+ </button>
52
+ </div>
53
+ </header>
54
+
55
+ <main className="app-main">
56
+ <Trace
57
+ commits={commits}
58
+ autoPlay={autoPlay}
59
+ interval={interval}
60
+ theme={theme}
61
+ filterable={filterable}
62
+ defaultFilter={filter}
63
+ onFilterChange={setFilter}
64
+ onCommit={(commit) => {
65
+ console.log('Viewing commit:', commit.message)
66
+ }}
67
+ />
68
+ </main>
69
+
70
+ <footer className="app-footer">
71
+ <span>Visualizing {commits.length} commits from the Trace repository</span>
72
+ <div className="kbd">
73
+ <kbd>←</kbd><kbd>→</kbd> navigate · <kbd>space</kbd> play/pause · <kbd>esc</kbd> reset
74
+ </div>
75
+ </footer>
76
+ </div>
77
+ )
78
+ }
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Trace — Git History Visualizer</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="./main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import { App } from './App'
4
+ import './styles.css'
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>
10
+ )
@@ -0,0 +1,170 @@
1
+ import type { Commit } from 'trace'
2
+
3
+ // Mock commits simulating the development of Trace itself
4
+ export const commits: Commit[] = [
5
+ {
6
+ hash: 'a1b2c3d',
7
+ message: 'Initial commit — add project skeleton',
8
+ author: 'Lecoo',
9
+ authorType: 'human',
10
+ time: '2h ago',
11
+ lines: [
12
+ { type: 'add', content: '{' },
13
+ { type: 'add', content: ' "name": "trace",' },
14
+ { type: 'add', content: ' "version": "0.1.0",' },
15
+ { type: 'add', content: ' "description": "Git history visualizer"' },
16
+ { type: 'add', content: '}' }
17
+ ]
18
+ },
19
+ {
20
+ hash: 'e4f5g6h',
21
+ message: 'Add Trace component with timeline view',
22
+ author: 'Lecoo',
23
+ authorType: 'human',
24
+ time: '1h ago',
25
+ lines: [
26
+ { type: 'ctx', content: 'export function Trace({ commits, autoPlay }: Props) {' },
27
+ { type: 'add', content: ' const [activeIndex, setActiveIndex] = useState(0)' },
28
+ { type: 'add', content: ' const [isPlaying, setIsPlaying] = useState(autoPlay)' },
29
+ { type: 'ctx', content: '' },
30
+ { type: 'add', content: ' return (' },
31
+ { type: 'add', content: ' <div className="trace-root">' },
32
+ { type: 'add', content: ' <Timeline commits={commits} active={activeIndex} />' },
33
+ { type: 'add', content: ' <DiffView commit={commits[activeIndex]} />' },
34
+ { type: 'add', content: ' </div>' },
35
+ { type: 'add', content: ' )' },
36
+ { type: 'ctx', content: '}' }
37
+ ]
38
+ },
39
+ {
40
+ hash: 'i7j8k9l',
41
+ message: 'Add AI detection patterns for Claude and Cursor',
42
+ author: 'Claude',
43
+ authorType: 'ai',
44
+ time: '45m ago',
45
+ lines: [
46
+ { type: 'remove', content: 'export const AI_PATTERNS = []' },
47
+ { type: 'add', content: 'export const DEFAULT_PATTERNS = {' },
48
+ { type: 'add', content: ' emails: [' },
49
+ { type: 'add', content: " 'noreply@cursor.sh'," },
50
+ { type: 'add', content: " 'claude@anthropic.com'," },
51
+ { type: 'add', content: " 'bot@github.com'" },
52
+ { type: 'add', content: ' ],' },
53
+ { type: 'add', content: ' messages: [' },
54
+ { type: 'add', content: " 'Co-Authored-By: Claude'," },
55
+ { type: 'add', content: " 'Co-Authored-By: Cursor'" },
56
+ { type: 'add', content: ' ]' },
57
+ { type: 'add', content: '}' }
58
+ ]
59
+ },
60
+ {
61
+ hash: 'm0n1o2p',
62
+ message: 'Implement keyboard navigation',
63
+ author: 'Claude',
64
+ authorType: 'ai',
65
+ time: '30m ago',
66
+ lines: [
67
+ { type: 'ctx', content: 'const handleKeyDown = (e: KeyboardEvent) => {' },
68
+ { type: 'add', content: ' switch (e.key) {' },
69
+ { type: 'add', content: ' case "ArrowUp":' },
70
+ { type: 'add', content: ' case "ArrowLeft":' },
71
+ { type: 'add', content: ' handlePrev()' },
72
+ { type: 'add', content: ' break' },
73
+ { type: 'add', content: ' case "ArrowDown":' },
74
+ { type: 'add', content: ' case "ArrowRight":' },
75
+ { type: 'add', content: ' handleNext()' },
76
+ { type: 'add', content: ' break' },
77
+ { type: 'add', content: ' case " ":' },
78
+ { type: 'add', content: ' togglePlay()' },
79
+ { type: 'add', content: ' break' },
80
+ { type: 'add', content: ' }' },
81
+ { type: 'ctx', content: '}' }
82
+ ]
83
+ },
84
+ {
85
+ hash: 'q3r4s5t',
86
+ message: 'Add theme support with CSS variables',
87
+ author: 'Lecoo',
88
+ authorType: 'human',
89
+ time: '15m ago',
90
+ lines: [
91
+ { type: 'ctx', content: 'export const themes = {' },
92
+ { type: 'add', content: ' dark: {' },
93
+ { type: 'add', content: ' bg: "#09090b",' },
94
+ { type: 'add', content: ' fg: "#e4e4e7",' },
95
+ { type: 'add', content: ' human: "#22c55e",' },
96
+ { type: 'add', content: ' ai: "#a855f7"' },
97
+ { type: 'add', content: ' },' },
98
+ { type: 'add', content: ' midnight: {' },
99
+ { type: 'add', content: ' bg: "#020617",' },
100
+ { type: 'add', content: ' fg: "#f1f5f9",' },
101
+ { type: 'add', content: ' human: "#10b981",' },
102
+ { type: 'add', content: ' ai: "#8b5cf6"' },
103
+ { type: 'add', content: ' }' },
104
+ { type: 'ctx', content: '}' }
105
+ ]
106
+ },
107
+ {
108
+ hash: 'u6v7w8x',
109
+ message: 'Add cyber theme with neon colors',
110
+ author: 'Claude',
111
+ authorType: 'ai',
112
+ time: '10m ago',
113
+ lines: [
114
+ { type: 'add', content: ' cyber: {' },
115
+ { type: 'add', content: ' bg: "#0a0a0f",' },
116
+ { type: 'add', content: ' fg: "#00ff9f",' },
117
+ { type: 'add', content: ' dim: "#00ff9f80",' },
118
+ { type: 'add', content: ' human: "#00ff9f",' },
119
+ { type: 'add', content: ' ai: "#ff00ff",' },
120
+ { type: 'add', content: ' add: "rgba(0, 255, 159, 0.15)",' },
121
+ { type: 'add', content: ' remove: "rgba(255, 0, 255, 0.15)"' },
122
+ { type: 'add', content: ' }' }
123
+ ]
124
+ },
125
+ {
126
+ hash: 'y9z0a1b',
127
+ message: 'Optimize rendering with memo and content-visibility',
128
+ author: 'Claude',
129
+ authorType: 'ai',
130
+ time: '5m ago',
131
+ lines: [
132
+ { type: 'ctx', content: 'const DiffLine = memo(function DiffLine({ line }: Props) {' },
133
+ { type: 'remove', content: ' return <div className="trace-line">{line.content}</div>' },
134
+ { type: 'add', content: ' return (' },
135
+ { type: 'add', content: ' <div className="trace-line" style={{' },
136
+ { type: 'add', content: ' contentVisibility: "auto",' },
137
+ { type: 'add', content: ' containIntrinsicSize: "auto 22px"' },
138
+ { type: 'add', content: ' }}>' },
139
+ { type: 'add', content: ' <span className="prefix">{line.type === "add" ? "+" : line.type === "remove" ? "-" : " "}</span>' },
140
+ { type: 'add', content: ' <span>{line.content}</span>' },
141
+ { type: 'add', content: ' </div>' },
142
+ { type: 'add', content: ' )' },
143
+ { type: 'ctx', content: '})' }
144
+ ]
145
+ },
146
+ {
147
+ hash: 'c2d3e4f',
148
+ message: 'Add CLI tool for exporting HTML',
149
+ author: 'Lecoo',
150
+ authorType: 'human',
151
+ time: 'just now',
152
+ lines: [
153
+ { type: 'add', content: '#!/usr/bin/env node' },
154
+ { type: 'ctx', content: '' },
155
+ { type: 'add', content: 'const args = process.argv.slice(2)' },
156
+ { type: 'add', content: 'const [file, format = "json"] = args' },
157
+ { type: 'ctx', content: '' },
158
+ { type: 'add', content: 'async function main() {' },
159
+ { type: 'add', content: ' const commits = await getGitHistory(file)' },
160
+ { type: 'add', content: ' if (format === "html") {' },
161
+ { type: 'add', content: ' console.log(exportHTML(commits))' },
162
+ { type: 'add', content: ' } else {' },
163
+ { type: 'add', content: ' console.log(JSON.stringify(commits, null, 2))' },
164
+ { type: 'add', content: ' }' },
165
+ { type: 'add', content: '}' },
166
+ { type: 'ctx', content: '' },
167
+ { type: 'add', content: 'main()' }
168
+ ]
169
+ }
170
+ ]
@@ -0,0 +1,103 @@
1
+ * {
2
+ box-sizing: border-box;
3
+ margin: 0;
4
+ padding: 0;
5
+ }
6
+
7
+ html, body, #root {
8
+ height: 100%;
9
+ }
10
+
11
+ body {
12
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
13
+ background: #09090b;
14
+ color: #e4e4e7;
15
+ }
16
+
17
+ .app {
18
+ height: 100%;
19
+ display: flex;
20
+ flex-direction: column;
21
+ }
22
+
23
+ .app-header {
24
+ padding: 16px 24px;
25
+ border-bottom: 1px solid #27272a;
26
+ display: flex;
27
+ align-items: center;
28
+ justify-content: space-between;
29
+ background: #09090b;
30
+ }
31
+
32
+ .app-title {
33
+ font-size: 18px;
34
+ font-weight: 600;
35
+ display: flex;
36
+ align-items: center;
37
+ gap: 12px;
38
+ }
39
+
40
+ .app-title span {
41
+ color: #a855f7;
42
+ }
43
+
44
+ .app-controls {
45
+ display: flex;
46
+ gap: 8px;
47
+ align-items: center;
48
+ }
49
+
50
+ .app-select {
51
+ background: #18181b;
52
+ border: 1px solid #27272a;
53
+ color: #e4e4e7;
54
+ padding: 6px 12px;
55
+ border-radius: 4px;
56
+ font-size: 13px;
57
+ cursor: pointer;
58
+ }
59
+
60
+ .app-select:hover {
61
+ border-color: #3f3f46;
62
+ }
63
+
64
+ .app-main {
65
+ flex: 1;
66
+ overflow: hidden;
67
+ }
68
+
69
+ .app-footer {
70
+ padding: 12px 24px;
71
+ border-top: 1px solid #27272a;
72
+ background: #09090b;
73
+ display: flex;
74
+ align-items: center;
75
+ justify-content: space-between;
76
+ font-size: 12px;
77
+ color: #71717a;
78
+ }
79
+
80
+ .app-footer a {
81
+ color: #a855f7;
82
+ text-decoration: none;
83
+ }
84
+
85
+ .app-footer a:hover {
86
+ text-decoration: underline;
87
+ }
88
+
89
+ .kbd {
90
+ display: inline-flex;
91
+ align-items: center;
92
+ gap: 4px;
93
+ margin-left: 16px;
94
+ }
95
+
96
+ .kbd kbd {
97
+ background: #18181b;
98
+ border: 1px solid #27272a;
99
+ padding: 2px 6px;
100
+ border-radius: 3px;
101
+ font-size: 11px;
102
+ font-family: 'JetBrains Mono', monospace;
103
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "resolveJsonModule": true,
11
+ "isolatedModules": true,
12
+ "noEmit": true,
13
+ "jsx": "react-jsx",
14
+ "strict": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+ "noFallthroughCasesInSwitch": true
18
+ },
19
+ "include": ["."],
20
+ "references": [{ "path": "./tsconfig.node.json" }]
21
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "skipLibCheck": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "allowSyntheticDefaultImports": true
8
+ },
9
+ "include": ["vite.config.ts"]
10
+ }
@@ -0,0 +1,20 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+ import { resolve } from 'path'
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ resolve: {
8
+ alias: {
9
+ 'trace': resolve(__dirname, '../../dist')
10
+ }
11
+ },
12
+ build: {
13
+ outDir: 'dist',
14
+ emptyOutDir: true
15
+ },
16
+ server: {
17
+ port: 3000,
18
+ open: true
19
+ }
20
+ })
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "git-trace",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Git history visualizer for the AI coding era",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js",
10
+ "require": "./dist/index.cjs"
11
+ },
12
+ "./highlight": {
13
+ "types": "./dist/highlight/index.d.ts",
14
+ "import": "./dist/highlight/index.js",
15
+ "require": "./dist/highlight/index.cjs"
16
+ },
17
+ "./config": {
18
+ "types": "./dist/config.d.ts",
19
+ "import": "./dist/config.js",
20
+ "require": "./dist/config.cjs"
21
+ }
22
+ },
23
+ "bin": {
24
+ "trace": "./dist/cli/index.js"
25
+ },
26
+ "peerDependencies": {
27
+ "react": ">=18"
28
+ },
29
+ "devDependencies": {
30
+ "@size-limit/preset-small-lib": "^12.0.1",
31
+ "@types/node": "^22.10.2",
32
+ "@types/react": "^19.0.6",
33
+ "@vitejs/plugin-react": "^4.3.4",
34
+ "bunchee": "^5.5.0",
35
+ "react": "^19.0.0",
36
+ "react-dom": "^19.0.0",
37
+ "size-limit": "^11.1.6",
38
+ "typescript": "^5.7.3",
39
+ "vite": "^6.0.7"
40
+ },
41
+ "scripts": {
42
+ "build": "bunchee && (cd cli && bunchee index.ts -o ../dist/cli/index.js -f esm --runtime node --external react)",
43
+ "dev": "bunchee --watch",
44
+ "demo": "vite examples/demo",
45
+ "demo:build": "vite build examples/demo",
46
+ "build:demo": "bun run build && vite build examples/demo",
47
+ "size": "size-limit",
48
+ "typecheck": "tsc --noEmit"
49
+ },
50
+ "size-limit": [
51
+ {
52
+ "path": "dist/index.js",
53
+ "limit": "3.1 KB",
54
+ "gzip": true,
55
+ "ignore": ["dist/config.js"]
56
+ }
57
+ ]
58
+ }