wukong-gitlog-cli 1.0.38 → 1.0.40
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/.eslintrc +1 -0
- package/.prettierrc +2 -1
- package/CHANGELOG.md +103 -0
- package/README.md +93 -173
- package/README.zh-CN.md +85 -137
- package/doc//347/233/256/345/275/225/347/273/223/346/236/204.md +2871 -0
- package/package.json +33 -29
- package/rc/.wukonggitlogrc +53 -0
- package/scripts/compareHourlyCounts.mjs +42 -0
- package/scripts/compareLatest.mjs +106 -0
- package/src/app/analyzeAction.mjs +120 -0
- package/src/app/exportAction.mjs +215 -0
- package/src/app/exportActionProgress.mjs +37 -0
- package/src/app/helpers.mjs +292 -0
- package/src/app/initAction.mjs +110 -0
- package/src/app/initActionWithTemp.mjs +192 -0
- package/src/app/journalAction.mjs +117 -0
- package/src/app/overtimeAction.mjs +100 -0
- package/src/app/runProfileEnd.mjs +0 -0
- package/src/app/serveAction.mjs +73 -0
- package/src/app/versionAction.mjs +7 -0
- package/src/cli/defineOptions.mjs +209 -0
- package/src/cli/index.mjs +0 -0
- package/src/cli/parseOptions.mjs +126 -8
- package/src/constants/index.mjs +16 -2
- package/src/domain/author/analyze.mjs +6 -0
- package/src/domain/author/map.mjs +0 -0
- package/src/domain/export/exportAuthor.mjs +28 -0
- package/src/domain/export/exportAuthorChanges.mjs +27 -0
- package/src/domain/export/exportAuthorChangesJson.mjs +31 -0
- package/src/domain/export/exportByMonth.mjs +157 -0
- package/src/domain/export/exportByWeek.mjs +121 -0
- package/src/domain/export/exportCommits.mjs +26 -0
- package/src/domain/export/exportCommitsExcel.mjs +45 -0
- package/src/domain/export/exportCommitsJson.mjs +31 -0
- package/src/domain/export/index.mjs +91 -0
- package/src/domain/git/ensureGitAvailable.mjs +66 -0
- package/src/domain/git/ensureGitRepo.mjs +41 -0
- package/src/domain/git/getGitFeatures.mjs +59 -0
- package/src/domain/git/getGitLogs.mjs +326 -0
- package/src/domain/git/getGitUser.mjs +44 -0
- package/src/domain/git/getRepoRoot.mjs +32 -0
- package/src/domain/git/gitCapability.mjs +119 -0
- package/src/domain/git/index.mjs +96 -0
- package/src/domain/git/resolveGerrit.mjs +102 -0
- package/src/domain/overtime/analyze.mjs +48 -0
- package/src/domain/overtime/index.mjs +3 -0
- package/src/domain/overtime/perPeriod.mjs +15 -0
- package/src/domain/overtime/render.mjs +15 -0
- package/src/i18n/index.mjs +38 -0
- package/src/i18n/resources.mjs +252 -0
- package/src/index.mjs +132 -649
- package/src/infra/cache.mjs +0 -0
- package/src/infra/configStore.mjs +128 -0
- package/src/infra/fs.mjs +0 -0
- package/src/infra/path.mjs +0 -0
- package/src/output/csv/overtime.mjs +12 -0
- package/src/output/csv.mjs +0 -0
- package/src/output/data/readData.mjs +54 -0
- package/src/output/data/writeData.mjs +145 -0
- package/src/output/excel/commits.mjs +9 -0
- package/src/output/excel/outputExcelDayReport.mjs +92 -0
- package/src/output/excel/perPeriod.mjs +24 -0
- package/src/{excel.mjs → output/excel.mjs} +3 -2
- package/src/output/index.mjs +79 -0
- package/src/output/json/overtime.mjs +9 -0
- package/src/output/tab/overtime.mjs +12 -0
- package/src/output/tab.mjs +0 -0
- package/src/output/text/commits.mjs +9 -0
- package/src/output/text/index.mjs +3 -0
- package/src/output/text/outputTxtDayReport.mjs +74 -0
- package/src/output/text/overtime.mjs +18 -0
- package/src/output/utils/getEsmJs.mjs +10 -0
- package/src/output/utils/index.mjs +14 -0
- package/src/output/utils/outputPath.mjs +19 -0
- package/src/output/utils/writeFile.mjs +10 -0
- package/src/serve/index.mjs +0 -0
- package/src/{server.mjs → serve/startServer.mjs} +21 -3
- package/src/serve/writeData.mjs +0 -0
- package/src/utils/authorNormalizer.mjs +28 -2
- package/src/utils/buildAuthorChangeStats.mjs +44 -0
- package/src/utils/deepMerge.mjs +13 -0
- package/src/utils/getPackage.mjs +11 -0
- package/src/utils/getProfileDirFile.mjs +12 -0
- package/src/utils/{file.mjs → groupRecords.mjs} +8 -9
- package/src/utils/index.mjs +5 -2
- package/src/utils/logger.mjs +28 -17
- package/src/utils/profiler.mjs +0 -101
- package/src/utils/resolve.mjs +11 -0
- package/src/utils/showVersionInfo.mjs +6 -2
- package/src/utils/time.mjs +0 -0
- package/src/utils/wait.mjs +2 -0
- package/web/app.js +3233 -260
- package/web/index.html +175 -22
- package/web/revoke/alpha1/app.js +4324 -0
- package/web/revoke/alpha1/index.html +266 -0
- package/web/revoke/app.before.js +3139 -0
- package/web/revoke/index-before.html +181 -0
- package/web/static/style.css +155 -9
- package/src/git.mjs +0 -256
- package/src/handlers/handleServe.mjs +0 -203
- package/src/lib/configStore.mjs +0 -11
- package/src/lib/memoize.mjs +0 -14
- package/src/utils/analyzeOvertimeCached.mjs +0 -7
- package/src/utils/checkUpdate.mjs +0 -130
- package/src/utils/exitWithTime.mjs +0 -17
- package/src/utils/handleSuccess.mjs +0 -9
- package/src/utils/logDev.mjs +0 -19
- package/src/utils/output.mjs +0 -26
- package/src/utils/profiler/diff.mjs +0 -26
- package/src/utils/profiler/format.mjs +0 -11
- package/src/utils/profiler/index.mjs +0 -144
- package/src/utils/profiler/trace.mjs +0 -26
- package/src/utils/time/scopeTimer.mjs +0 -37
- package/src/utils/time/timer.mjs +0 -33
- package/src/utils/time/withTimer.mjs +0 -11
- package/src/utils/timer.mjs +0 -35
- /package/src/{overtime → domain/overtime}/createOvertimeStats.mjs +0 -0
- /package/src/{overtime → domain/overtime}/overtime.mjs +0 -0
- /package/src/{json.mjs → output/json.mjs} +0 -0
- /package/src/{renderAuthorMapText.mjs → output/renderAuthorMapText.mjs} +0 -0
- /package/src/{stats-text.mjs → output/stats-text.mjs} +0 -0
- /package/src/{stats.mjs → output/stats.mjs} +0 -0
- /package/src/{text.mjs → output/text.mjs} +0 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
6
|
+
<title>wukong-gitlog-cli — Overtime Dashboard</title>
|
|
7
|
+
<link rel="stylesheet" href="./static/style.css" />
|
|
8
|
+
<script src="./static/echarts.min.js"></script>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<header>
|
|
12
|
+
<h1>Wukong Gitlog Overtime Dashboard</h1>
|
|
13
|
+
</header>
|
|
14
|
+
<main>
|
|
15
|
+
<section id="chartsHalf">
|
|
16
|
+
<div class="chart-card">
|
|
17
|
+
<h2>下班时间 vs 工作时间提交占比</h2>
|
|
18
|
+
<div id="outsideVsInsideChart" class="echart"></div>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="chart-card" id="kpiCard">
|
|
21
|
+
<h2>关键指标</h2>
|
|
22
|
+
<div id="kpiContent"></div>
|
|
23
|
+
</div>
|
|
24
|
+
</section>
|
|
25
|
+
<section id="charts">
|
|
26
|
+
<div class="chart-card">
|
|
27
|
+
<h2>每小时加班分布 (小时 -> 提交数)</h2>
|
|
28
|
+
<div id="hourlyOvertimeChart" data-title="每小时加班分布" class="echart"></div>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div class="chart-card">
|
|
32
|
+
<h2>按日提交趋势(次数)</h2>
|
|
33
|
+
<div id="dailyTrendChart" data-title="按日提交趋势(次数)" class="echart"></div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div class="chart-card">
|
|
37
|
+
<h2>每周趋势(加班占比)</h2>
|
|
38
|
+
<div id="weeklyTrendChart" data-title="每周趋势(加班占比)" class="echart"></div>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="chart-card">
|
|
41
|
+
<h2>每月趋势(加班占比)</h2>
|
|
42
|
+
<div id="monthlyTrendChart" data-title="每月趋势(加班占比)" class="echart"></div>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="chart-card">
|
|
45
|
+
<h2>每日最晚提交时间(小时)</h2>
|
|
46
|
+
<div id="latestHourDailyChart" data-title="每日最晚提交时间(小时)" class="echart"></div>
|
|
47
|
+
</div>
|
|
48
|
+
<div class="chart-card">
|
|
49
|
+
<h2>每日超过下班的小时数</h2>
|
|
50
|
+
<div id="dailySeverityChart" data-title="每日超过下班的小时数" class="echart"></div>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="chart-card">
|
|
53
|
+
<h2>按日提交趋势(次数)</h2>
|
|
54
|
+
<div id="dailyTrendChartDog" data-title="按日提交趋势" class="echart"></div>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="chart-card">
|
|
57
|
+
<div id="mostTiredDay"></div>
|
|
58
|
+
<div id="mostTiredWeek"></div>
|
|
59
|
+
<div id="mostTiredMonth"></div>
|
|
60
|
+
<div id="latestOvertimeDay"></div>
|
|
61
|
+
<div id="latestOvertimeWeek"></div>
|
|
62
|
+
<div id="latestOvertimeMonth"></div>
|
|
63
|
+
</div>
|
|
64
|
+
</section>
|
|
65
|
+
|
|
66
|
+
<div class="chart-card">
|
|
67
|
+
<h2>开发者 Changed 工作量趋势(行数)</h2>
|
|
68
|
+
|
|
69
|
+
<div id="tabs" class="tabs">
|
|
70
|
+
<button data-type="daily" class="active">按日</button>
|
|
71
|
+
<button data-type="weekly">按周</button>
|
|
72
|
+
<button data-type="monthly">按月</button>
|
|
73
|
+
</div>
|
|
74
|
+
<div id="chartAuthorChanges" class="echart"></div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div class="chart-card">
|
|
78
|
+
<h2>开发者 加班趋势(次数)</h2>
|
|
79
|
+
<div class="tabs" id="tabsOvertime">
|
|
80
|
+
<button data-type="daily" class="active">按日</button>
|
|
81
|
+
<button data-type="weekly">按周</button>
|
|
82
|
+
<button data-type="monthly">按月</button>
|
|
83
|
+
</div>
|
|
84
|
+
<div id="chartAuthorOvertime" class="echart"></div>
|
|
85
|
+
<div id="weeklyRiskSummary" class="risk-summary-box"></div>
|
|
86
|
+
<div id="monthlyRiskSummary" class="risk-summary-box"></div>
|
|
87
|
+
<div id="weeklyDurationRankSummary" class="risk-summary-box"></div>
|
|
88
|
+
<div id="weeklyDurationRiskSummary" class="risk-summary-box"></div>
|
|
89
|
+
<div id="monthlyDurationRankSummary" class="risk-summary-box"></div>
|
|
90
|
+
<div id="monthlyDurationRiskSummary" class="risk-summary-box"></div>
|
|
91
|
+
<div id="rolling30DurationRiskSummary" class="risk-summary-box"></div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div class="chart-card">
|
|
95
|
+
<h2>开发者 加班最晚趋势(小时)</h2>
|
|
96
|
+
<div class="tabs" id="tabsLatestOvertime">
|
|
97
|
+
<button data-type="daily" class="active">按日</button>
|
|
98
|
+
<button data-type="weekly">按周</button>
|
|
99
|
+
<button data-type="monthly">按月</button>
|
|
100
|
+
</div>
|
|
101
|
+
<div id="chartAuthorLatestOvertime" class="echart"></div>
|
|
102
|
+
<div id="latestRiskSummary" class="risk-summary-box"></div>
|
|
103
|
+
<div id="latestMonthlyRiskSummary" class="risk-summary-box"></div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div class="chart-card">
|
|
107
|
+
<h2>开发者 午休最晚提交(小时)</h2>
|
|
108
|
+
<div class="tabs" id="tabsLunch">
|
|
109
|
+
<button data-type="daily" class="active">按日</button>
|
|
110
|
+
<button data-type="weekly">按周</button>
|
|
111
|
+
<button data-type="monthly">按月</button>
|
|
112
|
+
</div>
|
|
113
|
+
<div id="chartAuthorLunch" class="echart"></div>
|
|
114
|
+
<div id="lunchWeeklyRankSummary" class="risk-summary-box"></div>
|
|
115
|
+
<div id="lunchWeeklyRiskSummary" class="risk-summary-box"></div>
|
|
116
|
+
<div id="lunchMonthlyRankSummary" class="risk-summary-box"></div>
|
|
117
|
+
<div id="lunchMonthlyRiskSummary" class="risk-summary-box"></div>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<section class="table-card">
|
|
121
|
+
<h2>提交清单</h2>
|
|
122
|
+
<div id="tableControls">
|
|
123
|
+
<input
|
|
124
|
+
id="searchInput"
|
|
125
|
+
type="search"
|
|
126
|
+
placeholder="搜索作者/信息/Hash/Date"
|
|
127
|
+
/>
|
|
128
|
+
<input type="date" id="startDate" />
|
|
129
|
+
<span>~</span>
|
|
130
|
+
<input type="date" id="endDate" />
|
|
131
|
+
<button id="clearDate">清除</button>
|
|
132
|
+
<label for="pageSize">每页显示</label>
|
|
133
|
+
<select id="pageSize">
|
|
134
|
+
<option value="10">10</option>
|
|
135
|
+
<option value="20">20</option>
|
|
136
|
+
<option value="50">50</option>
|
|
137
|
+
<option value="100">100</option>
|
|
138
|
+
</select>
|
|
139
|
+
<label id="commitsTotal"></label>
|
|
140
|
+
<div class="pager">
|
|
141
|
+
<button id="prevPage">上一页</button>
|
|
142
|
+
<span id="pageInfo"></span>
|
|
143
|
+
<button id="nextPage">下一页</button>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
<table id="commitsTable">
|
|
147
|
+
<thead>
|
|
148
|
+
<tr>
|
|
149
|
+
<th>Hash</th>
|
|
150
|
+
<th>Author</th>
|
|
151
|
+
<th>Email</th>
|
|
152
|
+
<th>Date</th>
|
|
153
|
+
<th>Message</th>
|
|
154
|
+
<th>Changed</th>
|
|
155
|
+
</tr>
|
|
156
|
+
</thead>
|
|
157
|
+
<tbody></tbody>
|
|
158
|
+
</table>
|
|
159
|
+
</section>
|
|
160
|
+
</main>
|
|
161
|
+
<footer>
|
|
162
|
+
<small
|
|
163
|
+
>Local dashboard generated by wukong-gitlog-cli. Data served from
|
|
164
|
+
<code>/data/</code>.</small
|
|
165
|
+
>
|
|
166
|
+
</footer>
|
|
167
|
+
<!-- 通用:右侧滑出的详情侧栏(小时 / 天 / 周 复用同一个 DOM) -->
|
|
168
|
+
<!-- 背景遮罩,点击可关闭侧栏 -->
|
|
169
|
+
<div id="sidebarBackdrop" class="sidebar-backdrop"></div>
|
|
170
|
+
<div id="dayDetailSidebar" class="sidebar">
|
|
171
|
+
<div class="sidebar-header">
|
|
172
|
+
<span id="sidebarDrawerTitle"></span>
|
|
173
|
+
<button id="sidebarClose">×</button>
|
|
174
|
+
</div>
|
|
175
|
+
<div id="sidebarTitle"></div>
|
|
176
|
+
<div id="sidebarContent" class="sidebar-content"></div>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<script type="module" src="/app.before.js"></script>
|
|
180
|
+
</body>
|
|
181
|
+
</html>
|
package/web/static/style.css
CHANGED
|
@@ -28,14 +28,27 @@ header h1 {
|
|
|
28
28
|
font-weight: 500;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/* 采样信息(显示在 header 旁边) */
|
|
32
|
+
.sampling-info {
|
|
33
|
+
margin-left: 16px;
|
|
34
|
+
font-size: 13px;
|
|
35
|
+
color: rgba(255, 255, 255, 0.95);
|
|
36
|
+
font-weight: 400;
|
|
37
|
+
opacity: 0.95;
|
|
38
|
+
}
|
|
39
|
+
.sampling {
|
|
40
|
+
color: #00a76f;
|
|
41
|
+
}
|
|
42
|
+
|
|
31
43
|
/* 主体布局 -------------------------------------------------- */
|
|
32
44
|
main {
|
|
33
45
|
display: flex;
|
|
34
46
|
flex-direction: column;
|
|
35
47
|
gap: 20px;
|
|
36
48
|
padding: 20px;
|
|
37
|
-
max-width: 1280px;
|
|
49
|
+
/* max-width: 1280px; */
|
|
38
50
|
margin: 20px auto;
|
|
51
|
+
overflow-x: hidden;
|
|
39
52
|
}
|
|
40
53
|
|
|
41
54
|
/* 卡片基础样式(MUI Paper) ----------------------------- */
|
|
@@ -58,10 +71,24 @@ main {
|
|
|
58
71
|
/* 图表网格 -------------------------------------------------- */
|
|
59
72
|
#charts {
|
|
60
73
|
display: grid;
|
|
61
|
-
|
|
62
|
-
|
|
74
|
+
/* 移动端默认:单列,防止溢出 */
|
|
75
|
+
grid-template-columns: 1fr;
|
|
76
|
+
gap: 20px;
|
|
77
|
+
padding: 16px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@media (min-width: 768px) {
|
|
81
|
+
#charts {
|
|
82
|
+
/* 桌面端:最小宽度 640px,自动填充 */
|
|
83
|
+
grid-template-columns: repeat(auto-fit, minmax(640px, 1fr));
|
|
84
|
+
}
|
|
63
85
|
}
|
|
64
|
-
|
|
86
|
+
/* 让核心趋势图始终占据整行,无论分几列 */
|
|
87
|
+
.full-width-chart {
|
|
88
|
+
grid-column: 1 / -1;
|
|
89
|
+
}
|
|
90
|
+
#chartsHalf,
|
|
91
|
+
.chartsHalf {
|
|
65
92
|
display: grid;
|
|
66
93
|
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
|
67
94
|
gap: 16px;
|
|
@@ -77,6 +104,8 @@ main {
|
|
|
77
104
|
height: 340px;
|
|
78
105
|
}
|
|
79
106
|
|
|
107
|
+
|
|
108
|
+
|
|
80
109
|
/* 表格 -------------------------------------------------- */
|
|
81
110
|
table {
|
|
82
111
|
width: 100%;
|
|
@@ -97,6 +126,18 @@ td {
|
|
|
97
126
|
border-bottom: 1px solid #eee;
|
|
98
127
|
}
|
|
99
128
|
|
|
129
|
+
@media screen and (max-width: 799px) {
|
|
130
|
+
.hash {
|
|
131
|
+
display: none;
|
|
132
|
+
}
|
|
133
|
+
.email {
|
|
134
|
+
display: none;
|
|
135
|
+
}
|
|
136
|
+
.cherryPick {
|
|
137
|
+
display: none;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
100
141
|
/* 分页、搜索栏 -------------------------------------------------- */
|
|
101
142
|
#tableControls {
|
|
102
143
|
display: flex;
|
|
@@ -139,6 +180,45 @@ td {
|
|
|
139
180
|
box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.15);
|
|
140
181
|
outline: none;
|
|
141
182
|
}
|
|
183
|
+
#tableControls input[type='date'] {
|
|
184
|
+
padding: 9px 12px;
|
|
185
|
+
border-radius: 8px;
|
|
186
|
+
border: 1px solid #ccc;
|
|
187
|
+
background: #fafafa;
|
|
188
|
+
font-size: 13px;
|
|
189
|
+
color: #111827;
|
|
190
|
+
transition:
|
|
191
|
+
border-color 0.2s,
|
|
192
|
+
box-shadow 0.2s;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
#tableControls input[type='date']:focus {
|
|
196
|
+
border-color: #1976d2;
|
|
197
|
+
box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.15);
|
|
198
|
+
outline: none;
|
|
199
|
+
}
|
|
200
|
+
/* 清除日期按钮 ------------------------------------------ */
|
|
201
|
+
#clearDate {
|
|
202
|
+
padding: 8px 10px;
|
|
203
|
+
border-radius: 8px;
|
|
204
|
+
border: none;
|
|
205
|
+
background: transparent;
|
|
206
|
+
color: #64748b; /* slate-500 */
|
|
207
|
+
font-size: 13px;
|
|
208
|
+
cursor: pointer;
|
|
209
|
+
transition:
|
|
210
|
+
color 0.2s ease,
|
|
211
|
+
background 0.2s ease;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
#clearDate:hover {
|
|
215
|
+
color: #ef4444; /* red-500 */
|
|
216
|
+
background: rgba(239, 68, 68, 0.08);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
#clearDate:active {
|
|
220
|
+
background: rgba(239, 68, 68, 0.16);
|
|
221
|
+
}
|
|
142
222
|
|
|
143
223
|
/* 按钮 — MUI Button 风格 */
|
|
144
224
|
.pager button {
|
|
@@ -335,8 +415,9 @@ td {
|
|
|
335
415
|
}
|
|
336
416
|
|
|
337
417
|
.sidebar-item-header .time {
|
|
338
|
-
font-family:
|
|
339
|
-
|
|
418
|
+
font-family:
|
|
419
|
+
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
|
|
420
|
+
'Courier New', monospace;
|
|
340
421
|
}
|
|
341
422
|
|
|
342
423
|
.sidebar-item-message {
|
|
@@ -382,6 +463,38 @@ td {
|
|
|
382
463
|
box-shadow: 0 3px 10px rgba(79, 70, 229, 0.25);
|
|
383
464
|
}
|
|
384
465
|
|
|
466
|
+
/* 开发者累计加班排名(小圆点样式) --------------------------- */
|
|
467
|
+
.rank-list {
|
|
468
|
+
margin-top: 12px;
|
|
469
|
+
display: flex;
|
|
470
|
+
flex-direction: column;
|
|
471
|
+
gap: 8px;
|
|
472
|
+
padding: 0;
|
|
473
|
+
}
|
|
474
|
+
.rank-list .rank-item {
|
|
475
|
+
display: flex;
|
|
476
|
+
align-items: center;
|
|
477
|
+
gap: 8px;
|
|
478
|
+
font-size: 14px;
|
|
479
|
+
color: #333;
|
|
480
|
+
}
|
|
481
|
+
.rank-list .dot {
|
|
482
|
+
width: 12px;
|
|
483
|
+
height: 12px;
|
|
484
|
+
border-radius: 50%;
|
|
485
|
+
display: inline-block;
|
|
486
|
+
margin-right: 8px;
|
|
487
|
+
/* flex: 0 0 12px; */
|
|
488
|
+
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.03) inset;
|
|
489
|
+
}
|
|
490
|
+
.rank-list .author {
|
|
491
|
+
flex: 1;
|
|
492
|
+
}
|
|
493
|
+
.rank-list .hours {
|
|
494
|
+
color: #666;
|
|
495
|
+
font-weight: 600;
|
|
496
|
+
}
|
|
497
|
+
|
|
385
498
|
/* 风险总结卡片 ---------------------------------------------- */
|
|
386
499
|
/* 风险总结卡片 — MUI风格 ---------------------------------------------- */
|
|
387
500
|
.risk-summary {
|
|
@@ -394,8 +507,9 @@ td {
|
|
|
394
507
|
border: 1px solid rgba(244, 199, 199, 0.9);
|
|
395
508
|
|
|
396
509
|
/* MUI 风格的柔和阴影 */
|
|
397
|
-
box-shadow:
|
|
398
|
-
|
|
510
|
+
box-shadow:
|
|
511
|
+
0px 3px 6px rgba(0, 0, 0, 0.08),
|
|
512
|
+
0px 1px 2px rgba(0, 0, 0, 0.04);
|
|
399
513
|
|
|
400
514
|
color: #b71c1c; /* 比 #991b1b 更接近 MUI error.dark */
|
|
401
515
|
font-size: 14px;
|
|
@@ -421,7 +535,39 @@ td {
|
|
|
421
535
|
margin-bottom: 6px;
|
|
422
536
|
}
|
|
423
537
|
|
|
424
|
-
|
|
538
|
+
.box-flex {
|
|
539
|
+
display: flex;
|
|
540
|
+
justify-content: space-between;
|
|
541
|
+
align-items: center;
|
|
542
|
+
}
|
|
543
|
+
.date {
|
|
544
|
+
display: flex;
|
|
545
|
+
width: 120px;
|
|
546
|
+
}
|
|
547
|
+
.long-txt{
|
|
548
|
+
/* overflow: hidden;
|
|
549
|
+
text-overflow: ellipsis; */
|
|
550
|
+
word-break: break-all;
|
|
551
|
+
/* 或 */
|
|
552
|
+
overflow-wrap: break-word;
|
|
553
|
+
word-wrap: break-word;
|
|
554
|
+
}
|
|
555
|
+
.tooltip-box{
|
|
556
|
+
width:500px;
|
|
557
|
+
}
|
|
558
|
+
.long-txt-break-all{
|
|
559
|
+
word-break: break-all;
|
|
560
|
+
overflow-wrap: break-word;
|
|
561
|
+
word-wrap: break-word;
|
|
562
|
+
text-overflow: ellipsis;
|
|
563
|
+
overflow: hidden;
|
|
564
|
+
max-width: 300px;
|
|
565
|
+
}
|
|
566
|
+
@media (min-width: 768px) {
|
|
567
|
+
.long-txt-break-all{
|
|
568
|
+
max-width: 500px;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
425
571
|
/* 底部 -------------------------------------------------- */
|
|
426
572
|
footer {
|
|
427
573
|
text-align: center;
|
package/src/git.mjs
DELETED
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
import { $ } from 'zx'
|
|
2
|
-
|
|
3
|
-
import { createAuthorNormalizer } from './utils/authorNormalizer.mjs'
|
|
4
|
-
|
|
5
|
-
const normalizer = createAuthorNormalizer()
|
|
6
|
-
|
|
7
|
-
export async function getGitLogsSlow(opts) {
|
|
8
|
-
const { author, email, since, until, limit, merges } = opts
|
|
9
|
-
|
|
10
|
-
const pretty = '%H%x1f%an%x1f%ae%x1f%ad%x1f%s%x1f%B%x1e'
|
|
11
|
-
const args = ['log', `--pretty=format:${pretty}`, '--date=iso-local']
|
|
12
|
-
|
|
13
|
-
if (author) args.push(`--author=${author}`)
|
|
14
|
-
if (email) args.push(`--author=${email}`)
|
|
15
|
-
if (since) args.push(`--since=${since}`)
|
|
16
|
-
if (until) args.push(`--until=${until}`)
|
|
17
|
-
if (merges === false) args.push(`--no-merges`)
|
|
18
|
-
if (limit) args.push(`-n`, `${limit}`)
|
|
19
|
-
|
|
20
|
-
const { stdout } = await $`git ${args}`.quiet()
|
|
21
|
-
|
|
22
|
-
const commits = stdout
|
|
23
|
-
.split('\x1e')
|
|
24
|
-
.filter(Boolean)
|
|
25
|
-
.map((r) => {
|
|
26
|
-
const f = r.split('\x1f').map((s) => (s || '').trim())
|
|
27
|
-
const hash = f[0]
|
|
28
|
-
const authorName = f[1]
|
|
29
|
-
const emailAddr = f[2]
|
|
30
|
-
const date = f[3]
|
|
31
|
-
const subject = f[4]
|
|
32
|
-
const body = f[5] || ''
|
|
33
|
-
|
|
34
|
-
const [, changeId] = body.match(/Change-Id:\s*(I[0-9a-fA-F]+)/) || []
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
hash,
|
|
38
|
-
author: normalizer.getAuthor(authorName, emailAddr),
|
|
39
|
-
originalAuthor: authorName,
|
|
40
|
-
email: emailAddr,
|
|
41
|
-
date,
|
|
42
|
-
message: subject,
|
|
43
|
-
body,
|
|
44
|
-
changeId
|
|
45
|
-
}
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
// === 新增:为每个 commit 计算代码增量 ===
|
|
49
|
-
for (const c of commits) {
|
|
50
|
-
try {
|
|
51
|
-
const { stdout: diffOut } =
|
|
52
|
-
await $`git show --numstat --format= ${c.hash}`.quiet()
|
|
53
|
-
// numstat 格式: "12 5 path/file.js"
|
|
54
|
-
const lines = diffOut
|
|
55
|
-
.split('\n')
|
|
56
|
-
.map((l) => l.trim())
|
|
57
|
-
.filter((l) => l && /^\d+\s+\d+/.test(l))
|
|
58
|
-
|
|
59
|
-
let added = 0
|
|
60
|
-
let deleted = 0
|
|
61
|
-
|
|
62
|
-
for (const line of lines) {
|
|
63
|
-
const [a, d] = line.split(/\s+/)
|
|
64
|
-
added += parseInt(a, 10) || 0
|
|
65
|
-
deleted += parseInt(d, 10) || 0
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
c.added = added
|
|
69
|
-
c.deleted = deleted
|
|
70
|
-
c.changed = added + deleted
|
|
71
|
-
} catch (err) {
|
|
72
|
-
// 避免阻塞,异常时改动量设置为0
|
|
73
|
-
c.added = 0
|
|
74
|
-
c.deleted = 0
|
|
75
|
-
c.changed = 0
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return { commits, authorMap: {} }
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export async function getGitLogsQuick(opts) {
|
|
83
|
-
const { author, email, since, until, limit, merges } = opts
|
|
84
|
-
|
|
85
|
-
const pretty = `${[
|
|
86
|
-
'%H', // hash
|
|
87
|
-
'%an', // author name
|
|
88
|
-
'%ae', // email
|
|
89
|
-
'%ad', // date
|
|
90
|
-
'%s', // subject
|
|
91
|
-
'%B' // body
|
|
92
|
-
].join('%x1f')}%x1e`
|
|
93
|
-
|
|
94
|
-
const args = [
|
|
95
|
-
'log',
|
|
96
|
-
`--pretty=format:${pretty}`,
|
|
97
|
-
'--date=iso-local',
|
|
98
|
-
'--numstat'
|
|
99
|
-
]
|
|
100
|
-
|
|
101
|
-
if (author) args.push(`--author=${author}`)
|
|
102
|
-
if (email) args.push(`--author=${email}`)
|
|
103
|
-
if (since) args.push(`--since=${since}`)
|
|
104
|
-
if (until) args.push(`--until=${until}`)
|
|
105
|
-
if (merges === false) args.push(`--no-merges`)
|
|
106
|
-
if (limit) args.push('-n', `${limit}`)
|
|
107
|
-
|
|
108
|
-
const { stdout } = await $`git ${args}`.quiet()
|
|
109
|
-
|
|
110
|
-
const rawCommits = stdout.split('\x1e').filter(Boolean)
|
|
111
|
-
const commits = []
|
|
112
|
-
|
|
113
|
-
for (const raw of rawCommits) {
|
|
114
|
-
const block = raw.replace(/\r/g, '').trim()
|
|
115
|
-
// eslint-disable-next-line no-continue
|
|
116
|
-
if (!block) continue
|
|
117
|
-
|
|
118
|
-
// header: 6 个字段用 \x1f 分隔
|
|
119
|
-
const headerMatch = block.match(
|
|
120
|
-
// eslint-disable-next-line no-control-regex
|
|
121
|
-
/^([^\x1f]*)\x1f([^\x1f]*)\x1f([^\x1f]*)\x1f([^\x1f]*)\x1f([^\x1f]*)\x1f([\s\S]*)$/
|
|
122
|
-
)
|
|
123
|
-
// eslint-disable-next-line no-continue
|
|
124
|
-
if (!headerMatch) continue
|
|
125
|
-
|
|
126
|
-
const [, hash, authorName, emailAddr, date, subject, body] = headerMatch
|
|
127
|
-
|
|
128
|
-
const [, changeId] = body.match(/Change-Id:\s*(I[0-9a-fA-F]+)/) || []
|
|
129
|
-
|
|
130
|
-
const c = {
|
|
131
|
-
hash,
|
|
132
|
-
author: normalizer.getAuthor(authorName, emailAddr),
|
|
133
|
-
originalAuthor: authorName,
|
|
134
|
-
email: emailAddr,
|
|
135
|
-
date,
|
|
136
|
-
message: subject,
|
|
137
|
-
body,
|
|
138
|
-
changeId,
|
|
139
|
-
added: 0,
|
|
140
|
-
deleted: 0,
|
|
141
|
-
changed: 0,
|
|
142
|
-
files: []
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// 匹配所有 numstat 行
|
|
146
|
-
const numstatLines = block
|
|
147
|
-
.split('\n')
|
|
148
|
-
.filter((l) => /^\d+\s+\d+\s+/.test(l))
|
|
149
|
-
for (const line of numstatLines) {
|
|
150
|
-
const [a, d, file] = line.trim().split(/\s+/)
|
|
151
|
-
const added = parseInt(a, 10) || 0
|
|
152
|
-
const deleted = parseInt(d, 10) || 0
|
|
153
|
-
c.added += added
|
|
154
|
-
c.deleted += deleted
|
|
155
|
-
c.changed += added + deleted
|
|
156
|
-
c.files.push({ file, added, deleted })
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
commits.push(c)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return {
|
|
163
|
-
commits,
|
|
164
|
-
authorMap: normalizer.getMap()
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* 高性能 git log + numstat 获取 commit
|
|
170
|
-
*/
|
|
171
|
-
export async function getGitLogsFast(opts = {}) {
|
|
172
|
-
const { author, email, since, until, limit, merges } = opts
|
|
173
|
-
|
|
174
|
-
const pretty = `${[
|
|
175
|
-
'%H', // hash
|
|
176
|
-
'%an', // author name
|
|
177
|
-
'%ae', // email
|
|
178
|
-
'%ad', // date
|
|
179
|
-
'%s', // subject
|
|
180
|
-
'%B' // body
|
|
181
|
-
].join('%x1f')}%x1e`
|
|
182
|
-
|
|
183
|
-
const args = [
|
|
184
|
-
'log',
|
|
185
|
-
`--pretty=format:${pretty}`,
|
|
186
|
-
'--date=iso-local',
|
|
187
|
-
'--numstat'
|
|
188
|
-
]
|
|
189
|
-
|
|
190
|
-
if (author) args.push(`--author=${author}`)
|
|
191
|
-
if (email) args.push(`--author=${email}`)
|
|
192
|
-
if (since) args.push(`--since=${since}`)
|
|
193
|
-
if (until) args.push(`--until=${until}`)
|
|
194
|
-
if (merges === false) args.push(`--no-merges`)
|
|
195
|
-
if (limit) args.push('-n', `${limit}`)
|
|
196
|
-
|
|
197
|
-
const { stdout } = await $`git ${args}`.quiet()
|
|
198
|
-
const raw = stdout.replace(/\r/g, '')
|
|
199
|
-
|
|
200
|
-
const commits = []
|
|
201
|
-
|
|
202
|
-
// 匹配每个 commit header + numstat
|
|
203
|
-
const commitRegex =
|
|
204
|
-
// eslint-disable-next-line no-control-regex
|
|
205
|
-
/([0-9a-f]+)\x1f([^\x1f]*)\x1f([^\x1f]*)\x1f([^\x1f]*)\x1f([^\x1f]*)\x1f([\s\S]*?)(?=(?:[0-9a-f]{7,40}\x1f)|\x1e$)/g
|
|
206
|
-
let match
|
|
207
|
-
|
|
208
|
-
for (const ns of raw.matchAll(commitRegex)) {
|
|
209
|
-
const [_, hash, authorName, emailAddr, date, subject, bodyAndNumstat] = ns
|
|
210
|
-
const [, changeId] =
|
|
211
|
-
bodyAndNumstat.match(/Change-Id:\s*(I[0-9a-fA-F]+)/) || []
|
|
212
|
-
|
|
213
|
-
const c = {
|
|
214
|
-
hash,
|
|
215
|
-
author: normalizer.getAuthor(authorName, emailAddr),
|
|
216
|
-
originalAuthor: authorName,
|
|
217
|
-
email: emailAddr,
|
|
218
|
-
date,
|
|
219
|
-
message: subject,
|
|
220
|
-
body: bodyAndNumstat,
|
|
221
|
-
changeId,
|
|
222
|
-
added: 0,
|
|
223
|
-
deleted: 0,
|
|
224
|
-
changed: 0,
|
|
225
|
-
files: []
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// 匹配 numstat
|
|
229
|
-
const numstatRegex = /^(\d+)\s+(\d+)\s+(.+)$/gm
|
|
230
|
-
for (const m of bodyAndNumstat.matchAll(numstatRegex)) {
|
|
231
|
-
const added = parseInt(m[1], 10) || 0
|
|
232
|
-
const deleted = parseInt(m[2], 10) || 0
|
|
233
|
-
const file = m[3]
|
|
234
|
-
c.added += added
|
|
235
|
-
c.deleted += deleted
|
|
236
|
-
c.changed += added + deleted
|
|
237
|
-
c.files.push({ file, added, deleted })
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
commits.push(c)
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// 最终统一覆盖 author,保证所有 commit 都使用中文名(如存在)
|
|
244
|
-
const finalMap = normalizer.getMap()
|
|
245
|
-
for (const c of commits) {
|
|
246
|
-
if (c.email && finalMap[c.email]) {
|
|
247
|
-
c.author = finalMap[c.email]
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return {
|
|
252
|
-
commits,
|
|
253
|
-
authorMap: finalMap,
|
|
254
|
-
originalMap: normalizer.getOriginalMap()
|
|
255
|
-
}
|
|
256
|
-
}
|