openspec-stat 1.1.0 → 1.3.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/README.md +44 -0
- package/README.zh-CN.md +44 -0
- package/dist/cjs/cli.js +12 -127
- package/dist/cjs/commands/init.d.ts +7 -0
- package/dist/cjs/commands/init.js +58 -0
- package/dist/cjs/commands/multi.d.ts +16 -0
- package/dist/cjs/commands/multi.js +172 -0
- package/dist/cjs/commands/single.d.ts +2 -0
- package/dist/cjs/commands/single.js +148 -0
- package/dist/cjs/formatters.d.ts +4 -4
- package/dist/cjs/formatters.js +268 -30
- package/dist/cjs/git-analyzer.d.ts +1 -0
- package/dist/cjs/git-analyzer.js +6 -0
- package/dist/cjs/i18n/locales/en.json +80 -1
- package/dist/cjs/i18n/locales/zh-CN.json +80 -1
- package/dist/cjs/multi/config-validator.d.ts +3 -0
- package/dist/cjs/multi/config-validator.js +130 -0
- package/dist/cjs/multi/config-wizard.d.ts +50 -0
- package/dist/cjs/multi/config-wizard.js +331 -0
- package/dist/cjs/multi/multi-repo-analyzer.d.ts +14 -0
- package/dist/cjs/multi/multi-repo-analyzer.js +210 -0
- package/dist/cjs/stats-aggregator.js +25 -0
- package/dist/cjs/types.d.ts +57 -0
- package/dist/esm/cli.js +43 -137
- package/dist/esm/commands/init.d.ts +7 -0
- package/dist/esm/commands/init.js +49 -0
- package/dist/esm/commands/multi.d.ts +16 -0
- package/dist/esm/commands/multi.js +192 -0
- package/dist/esm/commands/single.d.ts +2 -0
- package/dist/esm/commands/single.js +162 -0
- package/dist/esm/formatters.d.ts +4 -4
- package/dist/esm/formatters.js +361 -55
- package/dist/esm/git-analyzer.d.ts +1 -0
- package/dist/esm/git-analyzer.js +104 -77
- package/dist/esm/i18n/locales/en.json +80 -1
- package/dist/esm/i18n/locales/zh-CN.json +80 -1
- package/dist/esm/multi/config-validator.d.ts +3 -0
- package/dist/esm/multi/config-validator.js +109 -0
- package/dist/esm/multi/config-wizard.d.ts +50 -0
- package/dist/esm/multi/config-wizard.js +535 -0
- package/dist/esm/multi/multi-repo-analyzer.d.ts +14 -0
- package/dist/esm/multi/multi-repo-analyzer.js +446 -0
- package/dist/esm/stats-aggregator.js +29 -0
- package/dist/esm/types.d.ts +57 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,12 +11,14 @@ A CLI tool for tracking team members' OpenSpec proposals and code changes in Git
|
|
|
11
11
|
|
|
12
12
|
- ✅ Track Git commits within specified time ranges
|
|
13
13
|
- ✅ Identify commits containing both OpenSpec proposals and code changes
|
|
14
|
+
- ✅ **Proposal-based statistics summary** - Aggregate statistics by proposal to avoid merge commit bias
|
|
14
15
|
- ✅ Group statistics by author (commits, proposals, code changes)
|
|
15
16
|
- ✅ Support multiple branches and wildcard filtering
|
|
16
17
|
- ✅ Author name mapping (handle multiple Git accounts for the same person)
|
|
17
18
|
- ✅ Track only recently active members (default: 2 weeks)
|
|
18
19
|
- ✅ Multiple output formats: Table, JSON, CSV, Markdown
|
|
19
20
|
- ✅ Internationalization support: English and Chinese (简体中文)
|
|
21
|
+
- ✅ **🆕 Multi-repository mode (BETA)** - Analyze multiple local/remote repositories in one run
|
|
20
22
|
|
|
21
23
|
## Installation
|
|
22
24
|
|
|
@@ -48,6 +50,33 @@ openspec-stat
|
|
|
48
50
|
|
|
49
51
|
This will track commits in the default time range (yesterday 20:00 ~ today 20:00).
|
|
50
52
|
|
|
53
|
+
### Multi-Repository Mode (BETA)
|
|
54
|
+
|
|
55
|
+
⚠️ **Experimental Feature**: Multi-repository mode allows analyzing multiple repositories (local or remote) in a single run.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Initialize multi-repo configuration (interactive wizard)
|
|
59
|
+
openspec-stat init --multi
|
|
60
|
+
|
|
61
|
+
# Run multi-repo analysis
|
|
62
|
+
openspec-stat multi -c .openspec-stats.multi.json
|
|
63
|
+
|
|
64
|
+
# Run with detailed contributor statistics
|
|
65
|
+
openspec-stat multi -c .openspec-stats.multi.json --show-contributors
|
|
66
|
+
|
|
67
|
+
# Generate template
|
|
68
|
+
openspec-stat init --template multi -o config.json
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Perfect for team managers who:**
|
|
72
|
+
- Have local access to backend repos but not frontend repos
|
|
73
|
+
- Need to track contributions across multiple repositories
|
|
74
|
+
- Want combined statistics without running multiple commands
|
|
75
|
+
|
|
76
|
+
**Note**: By default, multi-repo mode only shows aggregated statistics to avoid information overload. Use `--show-contributors` to see detailed statistics for each contributor.
|
|
77
|
+
|
|
78
|
+
See [Multi-Repository Guide](./MULTI_REPO_GUIDE.md) for detailed documentation.
|
|
79
|
+
|
|
51
80
|
### Command Line Options
|
|
52
81
|
|
|
53
82
|
```bash
|
|
@@ -163,6 +192,11 @@ Statistics include:
|
|
|
163
192
|
- **Deletions**: Lines of code deleted
|
|
164
193
|
- **Net Changes**: Additions - Deletions
|
|
165
194
|
|
|
195
|
+
The tool provides two perspectives:
|
|
196
|
+
|
|
197
|
+
1. **Proposal Summary**: Aggregates statistics by proposal, showing total code changes per proposal and all contributors. This avoids statistical bias from merge commits.
|
|
198
|
+
2. **Author Summary**: Groups statistics by contributor, showing individual author contributions.
|
|
199
|
+
|
|
166
200
|
## Output Formats
|
|
167
201
|
|
|
168
202
|
### Table Format (Default)
|
|
@@ -173,6 +207,16 @@ Time Range: 2024-01-01 00:00:00 ~ 2024-01-31 23:59:59
|
|
|
173
207
|
Branches: origin/master
|
|
174
208
|
Total Commits: 15
|
|
175
209
|
|
|
210
|
+
📋 Proposal Summary (by proposal)
|
|
211
|
+
┌──────────────┬─────────┬──────────────────┬────────────┬───────────┬───────────┬─────────────┐
|
|
212
|
+
│ Proposal │ Commits │ Contributors │ Code Files │ Additions │ Deletions │ Net Changes │
|
|
213
|
+
├──────────────┼─────────┼──────────────────┼────────────┼───────────┼───────────┼─────────────┤
|
|
214
|
+
│ feature-123 │ 5 │ John Doe, Jane S.│ 30 │ +890 │ -234 │ +656 │
|
|
215
|
+
│ feature-456 │ 3 │ John Doe │ 15 │ +344 │ -100 │ +244 │
|
|
216
|
+
└──────────────┴─────────┴──────────────────┴────────────┴───────────┴───────────┴─────────────┘
|
|
217
|
+
📊 Total: 2 proposals | 8 commits | 45 files | +1234/-334 lines (net: +900)
|
|
218
|
+
|
|
219
|
+
👥 Author Summary (by contributor)
|
|
176
220
|
┌──────────┬─────────┬──────────────────┬────────────┬───────────┬───────────┬─────────────┐
|
|
177
221
|
│ Author │ Commits │ OpenSpec Proposals│ Code Files │ Additions │ Deletions │ Net Changes │
|
|
178
222
|
├──────────┼─────────┼──────────────────┼────────────┼───────────┼───────────┼─────────────┤
|
package/README.zh-CN.md
CHANGED
|
@@ -11,12 +11,14 @@
|
|
|
11
11
|
|
|
12
12
|
- ✅ 追踪指定时间范围内的 Git 提交
|
|
13
13
|
- ✅ 识别同时包含 OpenSpec 提案和代码变更的提交
|
|
14
|
+
- ✅ **提案维度统计汇总** - 按提案聚合统计,避免 merge commit 导致的统计偏差
|
|
14
15
|
- ✅ 按作者分组统计(提交数、提案数、代码变更)
|
|
15
16
|
- ✅ 支持多分支和通配符过滤
|
|
16
17
|
- ✅ 作者名称映射(处理同一人的多个 Git 账号)
|
|
17
18
|
- ✅ 仅追踪最近活跃的成员(默认:2 周)
|
|
18
19
|
- ✅ 多种输出格式:表格、JSON、CSV、Markdown
|
|
19
20
|
- ✅ 国际化支持:英文和中文
|
|
21
|
+
- ✅ **🆕 多仓库模式(BETA)** - 一次运行分析多个本地/远程仓库
|
|
20
22
|
|
|
21
23
|
## 安装
|
|
22
24
|
|
|
@@ -48,6 +50,33 @@ openspec-stat
|
|
|
48
50
|
|
|
49
51
|
这将追踪默认时间范围内的提交(昨天 20:00 ~ 今天 20:00)。
|
|
50
52
|
|
|
53
|
+
### 多仓库模式(BETA)
|
|
54
|
+
|
|
55
|
+
⚠️ **实验性功能**:多仓库模式允许在单次运行中分析多个仓库(本地或远程)。
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# 初始化多仓库配置(交互式向导)
|
|
59
|
+
openspec-stat init --multi
|
|
60
|
+
|
|
61
|
+
# 运行多仓库分析
|
|
62
|
+
openspec-stat multi -c .openspec-stats.multi.json
|
|
63
|
+
|
|
64
|
+
# 运行并显示详细的贡献者统计
|
|
65
|
+
openspec-stat multi -c .openspec-stats.multi.json --show-contributors
|
|
66
|
+
|
|
67
|
+
# 生成配置模板
|
|
68
|
+
openspec-stat init --template multi -o config.json
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**适用于以下场景的团队管理者:**
|
|
72
|
+
- 拥有后端仓库的本地访问权限,但没有前端仓库
|
|
73
|
+
- 需要跨多个仓库追踪贡献情况
|
|
74
|
+
- 希望获得合并统计结果,而无需运行多个命令
|
|
75
|
+
|
|
76
|
+
**注意**:默认情况下,多仓库模式仅显示聚合统计信息,以避免信息过载。使用 `--show-contributors` 可查看每个贡献者的详细统计信息。
|
|
77
|
+
|
|
78
|
+
详细文档请参阅 [多仓库模式指南](./MULTI_REPO_GUIDE.md)。
|
|
79
|
+
|
|
51
80
|
### 命令行选项
|
|
52
81
|
|
|
53
82
|
```bash
|
|
@@ -163,6 +192,11 @@ openspec-stat --lang zh-CN --verbose
|
|
|
163
192
|
- **删除行数**:删除的代码行数
|
|
164
193
|
- **净变更**:新增行数 - 删除行数
|
|
165
194
|
|
|
195
|
+
工具提供两个统计视角:
|
|
196
|
+
|
|
197
|
+
1. **提案汇总**:按提案聚合统计,显示每个提案的总代码变更量和所有贡献者,避免 merge commit 导致的统计偏差
|
|
198
|
+
2. **作者汇总**:按贡献者分组统计,显示各个作者的个人贡献情况
|
|
199
|
+
|
|
166
200
|
## 输出格式
|
|
167
201
|
|
|
168
202
|
### 表格格式(默认)
|
|
@@ -173,6 +207,16 @@ openspec-stat --lang zh-CN --verbose
|
|
|
173
207
|
分支:origin/master
|
|
174
208
|
总提交数:15
|
|
175
209
|
|
|
210
|
+
📋 提案汇总(按提案统计)
|
|
211
|
+
┌──────────────┬─────────┬──────────────────┬────────────┬───────────┬───────────┬─────────────┐
|
|
212
|
+
│ 提案 │ 提交数 │ 贡献者 │ 代码文件 │ 新增行数 │ 删除行数 │ 净变更 │
|
|
213
|
+
├──────────────┼─────────┼──────────────────┼────────────┼───────────┼───────────┼─────────────┤
|
|
214
|
+
│ feature-123 │ 5 │ 张三, 李四 │ 30 │ +890 │ -234 │ +656 │
|
|
215
|
+
│ feature-456 │ 3 │ 张三 │ 15 │ +344 │ -100 │ +244 │
|
|
216
|
+
└──────────────┴─────────┴──────────────────┴────────────┴───────────┴───────────┴─────────────┘
|
|
217
|
+
📊 总计:2 个提案 | 8 次提交 | 45 个文件 | +1234/-334 行(净变更:+900)
|
|
218
|
+
|
|
219
|
+
👥 作者汇总(按贡献者统计)
|
|
176
220
|
┌──────────┬─────────┬──────────────────┬────────────┬───────────┬───────────┬─────────────┐
|
|
177
221
|
│ 作者 │ 提交数 │ 提案数 │ 代码文件 │ 新增行数 │ 删除行数 │ 净变更 │
|
|
178
222
|
├──────────┼─────────┼──────────────────┼────────────┼───────────┼───────────┼─────────────┤
|
package/dist/cjs/cli.js
CHANGED
|
@@ -1,134 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __copyProps = (to, from, except, desc) => {
|
|
9
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
-
for (let key of __getOwnPropNames(from))
|
|
11
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
-
}
|
|
14
|
-
return to;
|
|
15
|
-
};
|
|
16
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
-
mod
|
|
23
|
-
));
|
|
24
2
|
|
|
25
3
|
// src/cli.ts
|
|
26
4
|
var import_commander = require("commander");
|
|
27
|
-
var
|
|
28
|
-
var
|
|
29
|
-
var
|
|
30
|
-
var import_stats_aggregator = require("./stats-aggregator.js");
|
|
31
|
-
var import_formatters = require("./formatters.js");
|
|
32
|
-
var import_time_utils = require("./time-utils.js");
|
|
33
|
-
var import_branch_selector = require("./branch-selector.js");
|
|
34
|
-
var import_i18n = require("./i18n/index.js");
|
|
5
|
+
var import_single = require("./commands/single.js");
|
|
6
|
+
var import_multi = require("./commands/multi.js");
|
|
7
|
+
var import_init = require("./commands/init.js");
|
|
35
8
|
var program = new import_commander.Command();
|
|
36
|
-
program.name("openspec-stat").description("Track team members' OpenSpec proposals and code changes in Git repositories").version("0.0.1")
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
until = options.until ? (0, import_time_utils.parseDateTime)(options.until) : (0, import_time_utils.getDefaultTimeRange)(config.defaultSinceHours, config.defaultUntilHours).until;
|
|
46
|
-
} else {
|
|
47
|
-
const defaultRange = (0, import_time_utils.getDefaultTimeRange)(config.defaultSinceHours, config.defaultUntilHours);
|
|
48
|
-
since = defaultRange.since;
|
|
49
|
-
until = defaultRange.until;
|
|
50
|
-
}
|
|
51
|
-
let branches;
|
|
52
|
-
if (options.branches) {
|
|
53
|
-
branches = (0, import_time_utils.parseBranches)(options.branches);
|
|
54
|
-
} else if (options.interactive !== false) {
|
|
55
|
-
branches = await (0, import_branch_selector.selectBranches)(options.repo, config.defaultBranches);
|
|
56
|
-
} else {
|
|
57
|
-
branches = config.defaultBranches || [];
|
|
58
|
-
}
|
|
59
|
-
console.log(
|
|
60
|
-
import_chalk.default.blue(
|
|
61
|
-
(0, import_i18n.t)("info.timeRange", {
|
|
62
|
-
since: since.toLocaleString(),
|
|
63
|
-
until: until.toLocaleString()
|
|
64
|
-
})
|
|
65
|
-
)
|
|
66
|
-
);
|
|
67
|
-
console.log(
|
|
68
|
-
import_chalk.default.blue(
|
|
69
|
-
(0, import_i18n.t)("info.branches", {
|
|
70
|
-
branches: branches.join(", ") || (0, import_i18n.t)("info.allBranches")
|
|
71
|
-
})
|
|
72
|
-
)
|
|
73
|
-
);
|
|
74
|
-
const analyzer = new import_git_analyzer.GitAnalyzer(options.repo, config);
|
|
75
|
-
console.log(import_chalk.default.blue((0, import_i18n.t)("loading.activeUsers")));
|
|
76
|
-
const activeAuthors = await analyzer.getActiveAuthors(config.activeUserWeeks || 2);
|
|
77
|
-
if (options.verbose) {
|
|
78
|
-
console.log(
|
|
79
|
-
import_chalk.default.gray(
|
|
80
|
-
(0, import_i18n.t)("info.activeUsers", {
|
|
81
|
-
weeks: String(config.activeUserWeeks || 2),
|
|
82
|
-
users: Array.from(activeAuthors).join(", ")
|
|
83
|
-
})
|
|
84
|
-
)
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
console.log(import_chalk.default.blue((0, import_i18n.t)("loading.analyzing")));
|
|
88
|
-
const commits = await analyzer.getCommits(since, until, branches);
|
|
89
|
-
if (commits.length === 0) {
|
|
90
|
-
console.log(import_chalk.default.yellow((0, import_i18n.t)("warning.noCommits")));
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
console.log(import_chalk.default.blue((0, import_i18n.t)("info.foundCommits", { count: String(commits.length) })));
|
|
94
|
-
const analyses = [];
|
|
95
|
-
for (let i = 0; i < commits.length; i++) {
|
|
96
|
-
const commit = commits[i];
|
|
97
|
-
if (options.verbose && i % 10 === 0) {
|
|
98
|
-
console.log(
|
|
99
|
-
import_chalk.default.gray(
|
|
100
|
-
(0, import_i18n.t)("info.analysisProgress", {
|
|
101
|
-
current: String(i + 1),
|
|
102
|
-
total: String(commits.length)
|
|
103
|
-
})
|
|
104
|
-
)
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
const analysis = await analyzer.analyzeCommit(commit);
|
|
108
|
-
if (analysis) {
|
|
109
|
-
analyses.push(analysis);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (analyses.length === 0) {
|
|
113
|
-
console.log(import_chalk.default.yellow((0, import_i18n.t)("warning.noQualifyingCommits")));
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
console.log(import_chalk.default.blue((0, import_i18n.t)("info.qualifyingCommits", { count: String(analyses.length) })));
|
|
117
|
-
const aggregator = new import_stats_aggregator.StatsAggregator(config, activeAuthors);
|
|
118
|
-
const result = aggregator.aggregate(analyses, since, until, branches, options.author);
|
|
119
|
-
const formatter = new import_formatters.OutputFormatter();
|
|
120
|
-
if (options.json) {
|
|
121
|
-
console.log(formatter.formatJSON(result));
|
|
122
|
-
} else if (options.csv) {
|
|
123
|
-
console.log(formatter.formatCSV(result));
|
|
124
|
-
} else if (options.markdown) {
|
|
125
|
-
console.log(formatter.formatMarkdown(result));
|
|
126
|
-
} else {
|
|
127
|
-
console.log(formatter.formatTable(result, options.verbose));
|
|
128
|
-
}
|
|
129
|
-
} catch (error) {
|
|
130
|
-
console.error(import_chalk.default.red((0, import_i18n.t)("error.prefix")), error);
|
|
131
|
-
process.exit(1);
|
|
132
|
-
}
|
|
9
|
+
program.name("openspec-stat").description("Track team members' OpenSpec proposals and code changes in Git repositories").version("0.0.1");
|
|
10
|
+
program.option("-r, --repo <path>", "Repository path", ".").option("-b, --branches <branches>", "Branch list, comma-separated").option("--no-interactive", "Disable interactive branch selection").option("-s, --since <datetime>", "Start time (default: yesterday 20:00)").option("-u, --until <datetime>", "End time (default: today 20:00)").option("-a, --author <name>", "Filter by specific author").option("--json", "Output in JSON format").option("--csv", "Output in CSV format").option("--markdown", "Output in Markdown format").option("-c, --config <path>", "Configuration file path").option("-v, --verbose", "Verbose output mode").option("-l, --lang <language>", "Language for output (en, zh-CN)", "en").option("--no-fetch", "Skip fetching remote branches").action(async (options) => {
|
|
11
|
+
await (0, import_single.runSingleRepoCommand)(options);
|
|
12
|
+
});
|
|
13
|
+
program.command("multi").description("Multi-repository analysis mode (BETA)").option("-c, --config <path>", "Configuration file path", ".openspec-stats.multi.json").option("-s, --since <datetime>", "Override start time").option("-u, --until <datetime>", "Override end time").option("-a, --author <name>", "Filter by specific author").option("--json", "Output in JSON format").option("--csv", "Output in CSV format").option("--markdown", "Output in Markdown format").option("-v, --verbose", "Verbose output mode").option("-l, --lang <language>", "Language (en, zh-CN)", "en").option("--no-cleanup", "Do not cleanup temporary directories").option("--show-contributors", "Show detailed contributor statistics (default: only show summary)").option("--no-fetch", "Skip fetching remote branches for local repositories").action(async (options) => {
|
|
14
|
+
await (0, import_multi.runMultiRepoCommand)(options);
|
|
15
|
+
});
|
|
16
|
+
program.command("init").description("Initialize configuration file").option("--multi", "Create multi-repository configuration (interactive)").option("--template <type>", "Generate template (single|multi)").option("-o, --output <path>", "Output file path").action(async (options) => {
|
|
17
|
+
await (0, import_init.runInitCommand)(options);
|
|
133
18
|
});
|
|
134
19
|
program.parse();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
|
|
29
|
+
// src/commands/init.ts
|
|
30
|
+
var init_exports = {};
|
|
31
|
+
__export(init_exports, {
|
|
32
|
+
runInitCommand: () => runInitCommand
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(init_exports);
|
|
35
|
+
var import_chalk = __toESM(require("chalk"));
|
|
36
|
+
var import_fs = require("fs");
|
|
37
|
+
var import_i18n = require("../i18n/index.js");
|
|
38
|
+
var import_config_wizard = require("../multi/config-wizard.js");
|
|
39
|
+
async function runInitCommand(options) {
|
|
40
|
+
try {
|
|
41
|
+
if (options.template) {
|
|
42
|
+
const template = options.template === "multi" ? import_config_wizard.MULTI_REPO_TEMPLATE : import_config_wizard.SINGLE_REPO_TEMPLATE;
|
|
43
|
+
const outputPath = options.output || `.openspec-stats.${options.template}.json`;
|
|
44
|
+
(0, import_fs.writeFileSync)(outputPath, JSON.stringify(template, null, 2));
|
|
45
|
+
console.log(import_chalk.default.green((0, import_i18n.t)("init.templateCreated", { path: outputPath })));
|
|
46
|
+
console.log(import_chalk.default.gray((0, import_i18n.t)("init.templateEdit")));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
await (0, import_config_wizard.runConfigWizard)(options.multi || false);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error(import_chalk.default.red((0, import_i18n.t)("error.prefix")), error);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
56
|
+
0 && (module.exports = {
|
|
57
|
+
runInitCommand
|
|
58
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface MultiCommandOptions {
|
|
2
|
+
config: string;
|
|
3
|
+
since?: string;
|
|
4
|
+
until?: string;
|
|
5
|
+
author?: string;
|
|
6
|
+
json?: boolean;
|
|
7
|
+
csv?: boolean;
|
|
8
|
+
markdown?: boolean;
|
|
9
|
+
verbose?: boolean;
|
|
10
|
+
lang?: string;
|
|
11
|
+
cleanup?: boolean;
|
|
12
|
+
showContributors?: boolean;
|
|
13
|
+
noFetch?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function runMultiRepoCommand(options: MultiCommandOptions): Promise<void>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
|
|
29
|
+
// src/commands/multi.ts
|
|
30
|
+
var multi_exports = {};
|
|
31
|
+
__export(multi_exports, {
|
|
32
|
+
runMultiRepoCommand: () => runMultiRepoCommand
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(multi_exports);
|
|
35
|
+
var import_chalk = __toESM(require("chalk"));
|
|
36
|
+
var import_fs = require("fs");
|
|
37
|
+
var import_path = require("path");
|
|
38
|
+
var import_i18n = require("../i18n/index.js");
|
|
39
|
+
var import_multi_repo_analyzer = require("../multi/multi-repo-analyzer.js");
|
|
40
|
+
var import_config_validator = require("../multi/config-validator.js");
|
|
41
|
+
var import_stats_aggregator = require("../stats-aggregator.js");
|
|
42
|
+
var import_formatters = require("../formatters.js");
|
|
43
|
+
var import_time_utils = require("../time-utils.js");
|
|
44
|
+
async function runMultiRepoCommand(options) {
|
|
45
|
+
try {
|
|
46
|
+
(0, import_i18n.initI18n)(options.lang || "en");
|
|
47
|
+
console.log(import_chalk.default.yellow.bold((0, import_i18n.t)("multi.beta.warning")));
|
|
48
|
+
console.log(import_chalk.default.gray((0, import_i18n.t)("multi.beta.feedback")));
|
|
49
|
+
console.log();
|
|
50
|
+
console.log(import_chalk.default.blue((0, import_i18n.t)("multi.loading.config")));
|
|
51
|
+
const configPath = (0, import_path.resolve)(process.cwd(), options.config);
|
|
52
|
+
if (!(0, import_fs.existsSync)(configPath)) {
|
|
53
|
+
throw new Error(`Configuration file not found: ${configPath}`);
|
|
54
|
+
}
|
|
55
|
+
const rawConfig = JSON.parse((0, import_fs.readFileSync)(configPath, "utf-8"));
|
|
56
|
+
const config = (0, import_config_validator.validateAndFillDefaults)(rawConfig);
|
|
57
|
+
if (options.verbose) {
|
|
58
|
+
(0, import_config_validator.printConfigSummary)(config);
|
|
59
|
+
}
|
|
60
|
+
let since;
|
|
61
|
+
let until;
|
|
62
|
+
if (options.since || options.until) {
|
|
63
|
+
since = options.since ? (0, import_time_utils.parseDateTime)(options.since) : (0, import_time_utils.getDefaultTimeRange)(config.defaultSinceHours, config.defaultUntilHours).since;
|
|
64
|
+
until = options.until ? (0, import_time_utils.parseDateTime)(options.until) : (0, import_time_utils.getDefaultTimeRange)(config.defaultSinceHours, config.defaultUntilHours).until;
|
|
65
|
+
} else {
|
|
66
|
+
const defaultRange = (0, import_time_utils.getDefaultTimeRange)(config.defaultSinceHours, config.defaultUntilHours);
|
|
67
|
+
since = defaultRange.since;
|
|
68
|
+
until = defaultRange.until;
|
|
69
|
+
}
|
|
70
|
+
console.log(
|
|
71
|
+
import_chalk.default.blue(
|
|
72
|
+
(0, import_i18n.t)("info.timeRange", {
|
|
73
|
+
since: since.toLocaleString(),
|
|
74
|
+
until: until.toLocaleString()
|
|
75
|
+
})
|
|
76
|
+
)
|
|
77
|
+
);
|
|
78
|
+
const analyzer = new import_multi_repo_analyzer.MultiRepoAnalyzer(config);
|
|
79
|
+
analyzer.registerCleanupHandlers();
|
|
80
|
+
if (options.cleanup === false) {
|
|
81
|
+
config.remoteCache = config.remoteCache || {
|
|
82
|
+
dir: "/tmp/openspec-stat-cache",
|
|
83
|
+
autoCleanup: false,
|
|
84
|
+
cleanupOnComplete: false,
|
|
85
|
+
cleanupOnError: false
|
|
86
|
+
};
|
|
87
|
+
config.remoteCache.cleanupOnComplete = false;
|
|
88
|
+
}
|
|
89
|
+
if (options.noFetch) {
|
|
90
|
+
config.autoFetch = false;
|
|
91
|
+
}
|
|
92
|
+
const repoResults = await analyzer.analyzeAll(since, until);
|
|
93
|
+
const successResults = repoResults.filter((r) => r.success);
|
|
94
|
+
const failedResults = repoResults.filter((r) => !r.success);
|
|
95
|
+
console.log(
|
|
96
|
+
import_chalk.default.blue(
|
|
97
|
+
(0, import_i18n.t)("multi.summary.title") + (0, import_i18n.t)("multi.summary.repos", {
|
|
98
|
+
total: String(repoResults.length),
|
|
99
|
+
success: String(successResults.length),
|
|
100
|
+
failed: String(failedResults.length)
|
|
101
|
+
})
|
|
102
|
+
)
|
|
103
|
+
);
|
|
104
|
+
if (failedResults.length > 0) {
|
|
105
|
+
console.log(import_chalk.default.yellow("\nFailed repositories:"));
|
|
106
|
+
failedResults.forEach((r) => {
|
|
107
|
+
console.log(import_chalk.default.red(` - ${r.repository}: ${r.error}`));
|
|
108
|
+
});
|
|
109
|
+
console.log();
|
|
110
|
+
}
|
|
111
|
+
const allAnalyses = successResults.flatMap((r) => r.analyses);
|
|
112
|
+
if (allAnalyses.length === 0) {
|
|
113
|
+
console.log(import_chalk.default.yellow((0, import_i18n.t)("warning.noQualifyingCommits")));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
console.log(import_chalk.default.blue((0, import_i18n.t)("info.qualifyingCommits", { count: String(allAnalyses.length) })));
|
|
117
|
+
console.log(import_chalk.default.blue((0, import_i18n.t)("loading.activeUsers")));
|
|
118
|
+
const activeAuthors = await getActiveAuthorsFromMultiRepo(config, repoResults);
|
|
119
|
+
if (options.verbose && activeAuthors.size > 0) {
|
|
120
|
+
console.log(
|
|
121
|
+
import_chalk.default.gray(
|
|
122
|
+
(0, import_i18n.t)("info.activeUsers", {
|
|
123
|
+
weeks: String(config.activeUserWeeks || 2),
|
|
124
|
+
users: Array.from(activeAuthors).join(", ")
|
|
125
|
+
})
|
|
126
|
+
)
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
const aggregator = new import_stats_aggregator.StatsAggregator(config, activeAuthors);
|
|
130
|
+
const allBranches = [
|
|
131
|
+
...new Set(
|
|
132
|
+
repoResults.flatMap((r) => {
|
|
133
|
+
var _a, _b;
|
|
134
|
+
return ((_b = (_a = config.repositories) == null ? void 0 : _a.find((repo) => repo.name === r.repository)) == null ? void 0 : _b.branches) || [];
|
|
135
|
+
})
|
|
136
|
+
)
|
|
137
|
+
];
|
|
138
|
+
const result = aggregator.aggregate(allAnalyses, since, until, allBranches, options.author);
|
|
139
|
+
const formatter = new import_formatters.OutputFormatter();
|
|
140
|
+
const showContributors = options.showContributors || false;
|
|
141
|
+
if (options.json) {
|
|
142
|
+
console.log(formatter.formatJSON(result, showContributors));
|
|
143
|
+
} else if (options.csv) {
|
|
144
|
+
console.log(formatter.formatCSV(result, showContributors));
|
|
145
|
+
} else if (options.markdown) {
|
|
146
|
+
console.log(formatter.formatMarkdown(result, showContributors));
|
|
147
|
+
} else {
|
|
148
|
+
console.log(formatter.formatTable(result, options.verbose, showContributors));
|
|
149
|
+
}
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error(import_chalk.default.red((0, import_i18n.t)("error.prefix")), error);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async function getActiveAuthorsFromMultiRepo(config, repoResults) {
|
|
156
|
+
const allAuthors = /* @__PURE__ */ new Set();
|
|
157
|
+
for (const result of repoResults) {
|
|
158
|
+
if (result.success && result.analyses) {
|
|
159
|
+
result.analyses.forEach((analysis) => {
|
|
160
|
+
var _a;
|
|
161
|
+
if ((_a = analysis.commit) == null ? void 0 : _a.author) {
|
|
162
|
+
allAuthors.add(analysis.commit.author);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return allAuthors;
|
|
168
|
+
}
|
|
169
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
170
|
+
0 && (module.exports = {
|
|
171
|
+
runMultiRepoCommand
|
|
172
|
+
});
|