openspec-stat 1.2.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 +28 -0
- package/README.zh-CN.md +28 -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 +128 -36
- package/dist/cjs/git-analyzer.d.ts +1 -0
- package/dist/cjs/git-analyzer.js +6 -0
- package/dist/cjs/i18n/locales/en.json +74 -1
- package/dist/cjs/i18n/locales/zh-CN.json +74 -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/types.d.ts +46 -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 +173 -52
- package/dist/esm/git-analyzer.d.ts +1 -0
- package/dist/esm/git-analyzer.js +104 -77
- package/dist/esm/i18n/locales/en.json +74 -1
- package/dist/esm/i18n/locales/zh-CN.json +74 -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/types.d.ts +46 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ A CLI tool for tracking team members' OpenSpec proposals and code changes in Git
|
|
|
18
18
|
- ✅ Track only recently active members (default: 2 weeks)
|
|
19
19
|
- ✅ Multiple output formats: Table, JSON, CSV, Markdown
|
|
20
20
|
- ✅ Internationalization support: English and Chinese (简体中文)
|
|
21
|
+
- ✅ **🆕 Multi-repository mode (BETA)** - Analyze multiple local/remote repositories in one run
|
|
21
22
|
|
|
22
23
|
## Installation
|
|
23
24
|
|
|
@@ -49,6 +50,33 @@ openspec-stat
|
|
|
49
50
|
|
|
50
51
|
This will track commits in the default time range (yesterday 20:00 ~ today 20:00).
|
|
51
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
|
+
|
|
52
80
|
### Command Line Options
|
|
53
81
|
|
|
54
82
|
```bash
|
package/README.zh-CN.md
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
- ✅ 仅追踪最近活跃的成员(默认:2 周)
|
|
19
19
|
- ✅ 多种输出格式:表格、JSON、CSV、Markdown
|
|
20
20
|
- ✅ 国际化支持:英文和中文
|
|
21
|
+
- ✅ **🆕 多仓库模式(BETA)** - 一次运行分析多个本地/远程仓库
|
|
21
22
|
|
|
22
23
|
## 安装
|
|
23
24
|
|
|
@@ -49,6 +50,33 @@ openspec-stat
|
|
|
49
50
|
|
|
50
51
|
这将追踪默认时间范围内的提交(昨天 20:00 ~ 今天 20:00)。
|
|
51
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
|
+
|
|
52
80
|
### 命令行选项
|
|
53
81
|
|
|
54
82
|
```bash
|
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
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
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/single.ts
|
|
30
|
+
var single_exports = {};
|
|
31
|
+
__export(single_exports, {
|
|
32
|
+
runSingleRepoCommand: () => runSingleRepoCommand
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(single_exports);
|
|
35
|
+
var import_chalk = __toESM(require("chalk"));
|
|
36
|
+
var import_config = require("../config.js");
|
|
37
|
+
var import_git_analyzer = require("../git-analyzer.js");
|
|
38
|
+
var import_stats_aggregator = require("../stats-aggregator.js");
|
|
39
|
+
var import_formatters = require("../formatters.js");
|
|
40
|
+
var import_time_utils = require("../time-utils.js");
|
|
41
|
+
var import_branch_selector = require("../branch-selector.js");
|
|
42
|
+
var import_i18n = require("../i18n/index.js");
|
|
43
|
+
async function runSingleRepoCommand(options) {
|
|
44
|
+
try {
|
|
45
|
+
(0, import_i18n.initI18n)(options.lang);
|
|
46
|
+
console.log(import_chalk.default.blue((0, import_i18n.t)("loading.config")));
|
|
47
|
+
const config = await (0, import_config.loadConfig)(options.config, options.repo);
|
|
48
|
+
let since;
|
|
49
|
+
let until;
|
|
50
|
+
if (options.since || options.until) {
|
|
51
|
+
since = options.since ? (0, import_time_utils.parseDateTime)(options.since) : (0, import_time_utils.getDefaultTimeRange)(config.defaultSinceHours, config.defaultUntilHours).since;
|
|
52
|
+
until = options.until ? (0, import_time_utils.parseDateTime)(options.until) : (0, import_time_utils.getDefaultTimeRange)(config.defaultSinceHours, config.defaultUntilHours).until;
|
|
53
|
+
} else {
|
|
54
|
+
const defaultRange = (0, import_time_utils.getDefaultTimeRange)(config.defaultSinceHours, config.defaultUntilHours);
|
|
55
|
+
since = defaultRange.since;
|
|
56
|
+
until = defaultRange.until;
|
|
57
|
+
}
|
|
58
|
+
let branches;
|
|
59
|
+
if (options.branches) {
|
|
60
|
+
branches = (0, import_time_utils.parseBranches)(options.branches);
|
|
61
|
+
} else if (options.interactive !== false) {
|
|
62
|
+
branches = await (0, import_branch_selector.selectBranches)(options.repo, config.defaultBranches);
|
|
63
|
+
} else {
|
|
64
|
+
branches = config.defaultBranches || [];
|
|
65
|
+
}
|
|
66
|
+
console.log(
|
|
67
|
+
import_chalk.default.blue(
|
|
68
|
+
(0, import_i18n.t)("info.timeRange", {
|
|
69
|
+
since: since.toLocaleString(),
|
|
70
|
+
until: until.toLocaleString()
|
|
71
|
+
})
|
|
72
|
+
)
|
|
73
|
+
);
|
|
74
|
+
console.log(
|
|
75
|
+
import_chalk.default.blue(
|
|
76
|
+
(0, import_i18n.t)("info.branches", {
|
|
77
|
+
branches: branches.join(", ") || (0, import_i18n.t)("info.allBranches")
|
|
78
|
+
})
|
|
79
|
+
)
|
|
80
|
+
);
|
|
81
|
+
const analyzer = new import_git_analyzer.GitAnalyzer(options.repo, config);
|
|
82
|
+
if (!options.noFetch && config.autoFetch !== false) {
|
|
83
|
+
console.log(import_chalk.default.blue((0, import_i18n.t)("loading.fetching")));
|
|
84
|
+
await analyzer.fetchRemote();
|
|
85
|
+
}
|
|
86
|
+
console.log(import_chalk.default.blue((0, import_i18n.t)("loading.activeUsers")));
|
|
87
|
+
const activeAuthors = await analyzer.getActiveAuthors(config.activeUserWeeks || 2);
|
|
88
|
+
if (options.verbose) {
|
|
89
|
+
console.log(
|
|
90
|
+
import_chalk.default.gray(
|
|
91
|
+
(0, import_i18n.t)("info.activeUsers", {
|
|
92
|
+
weeks: String(config.activeUserWeeks || 2),
|
|
93
|
+
users: Array.from(activeAuthors).join(", ")
|
|
94
|
+
})
|
|
95
|
+
)
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
console.log(import_chalk.default.blue((0, import_i18n.t)("loading.analyzing")));
|
|
99
|
+
const commits = await analyzer.getCommits(since, until, branches);
|
|
100
|
+
if (commits.length === 0) {
|
|
101
|
+
console.log(import_chalk.default.yellow((0, import_i18n.t)("warning.noCommits")));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
console.log(import_chalk.default.blue((0, import_i18n.t)("info.foundCommits", { count: String(commits.length) })));
|
|
105
|
+
const analyses = [];
|
|
106
|
+
for (let i = 0; i < commits.length; i++) {
|
|
107
|
+
const commit = commits[i];
|
|
108
|
+
if (options.verbose && i % 10 === 0) {
|
|
109
|
+
console.log(
|
|
110
|
+
import_chalk.default.gray(
|
|
111
|
+
(0, import_i18n.t)("info.analysisProgress", {
|
|
112
|
+
current: String(i + 1),
|
|
113
|
+
total: String(commits.length)
|
|
114
|
+
})
|
|
115
|
+
)
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
const analysis = await analyzer.analyzeCommit(commit);
|
|
119
|
+
if (analysis) {
|
|
120
|
+
analyses.push(analysis);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (analyses.length === 0) {
|
|
124
|
+
console.log(import_chalk.default.yellow((0, import_i18n.t)("warning.noQualifyingCommits")));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
console.log(import_chalk.default.blue((0, import_i18n.t)("info.qualifyingCommits", { count: String(analyses.length) })));
|
|
128
|
+
const aggregator = new import_stats_aggregator.StatsAggregator(config, activeAuthors);
|
|
129
|
+
const result = aggregator.aggregate(analyses, since, until, branches, options.author);
|
|
130
|
+
const formatter = new import_formatters.OutputFormatter();
|
|
131
|
+
if (options.json) {
|
|
132
|
+
console.log(formatter.formatJSON(result));
|
|
133
|
+
} else if (options.csv) {
|
|
134
|
+
console.log(formatter.formatCSV(result));
|
|
135
|
+
} else if (options.markdown) {
|
|
136
|
+
console.log(formatter.formatMarkdown(result));
|
|
137
|
+
} else {
|
|
138
|
+
console.log(formatter.formatTable(result, options.verbose));
|
|
139
|
+
}
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error(import_chalk.default.red((0, import_i18n.t)("error.prefix")), error);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
146
|
+
0 && (module.exports = {
|
|
147
|
+
runSingleRepoCommand
|
|
148
|
+
});
|
package/dist/cjs/formatters.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { StatsResult } from './types.js';
|
|
2
2
|
export declare class OutputFormatter {
|
|
3
|
-
formatTable(result: StatsResult, verbose?: boolean): string;
|
|
4
|
-
formatJSON(result: StatsResult): string;
|
|
5
|
-
formatCSV(result: StatsResult): string;
|
|
6
|
-
formatMarkdown(result: StatsResult): string;
|
|
3
|
+
formatTable(result: StatsResult, verbose?: boolean, showContributors?: boolean): string;
|
|
4
|
+
formatJSON(result: StatsResult, showContributors?: boolean): string;
|
|
5
|
+
formatCSV(result: StatsResult, showContributors?: boolean): string;
|
|
6
|
+
formatMarkdown(result: StatsResult, showContributors?: boolean): string;
|
|
7
7
|
}
|