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
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"loading.config": "🔍 正在加载配置...",
|
|
17
17
|
"loading.activeUsers": "🔍 正在获取活跃用户...",
|
|
18
18
|
"loading.analyzing": "🔍 正在分析提交历史...",
|
|
19
|
+
"loading.fetching": "🔄 正在拉取远程分支...",
|
|
19
20
|
|
|
20
21
|
"info.timeRange": "📅 时间范围:{{since}} ~ {{until}}",
|
|
21
22
|
"info.branches": "🌿 分支:{{branches}}",
|
|
@@ -48,11 +49,18 @@
|
|
|
48
49
|
"output.branches": "分支:{{branches}}\n",
|
|
49
50
|
"output.totalCommits": "总提交数:{{count}}\n\n",
|
|
50
51
|
"output.proposals": " 提案:{{proposals}}\n",
|
|
52
|
+
"output.proposalSummary": "📋 提案汇总(按提案统计)",
|
|
53
|
+
"output.proposalTotal": " 📊 总计:{{count}} 个提案 | {{commits}} 次提交 | {{files}} 个文件 | +{{additions}}/-{{deletions}} 行(净变更:{{netChanges}})\n",
|
|
54
|
+
"output.proposalTotalLabel": "提案汇总总计",
|
|
55
|
+
"output.authorSummary": "👥 作者汇总(按贡献者统计)",
|
|
56
|
+
"output.contributorHint": "💡 使用 --show-contributors 选项可查看每个贡献者的详细统计信息",
|
|
51
57
|
|
|
52
58
|
"table.branch": "分支",
|
|
53
59
|
"table.period": "周期",
|
|
54
60
|
"table.commits": "提交数",
|
|
55
61
|
"table.proposals": "提案数",
|
|
62
|
+
"table.proposal": "提案",
|
|
63
|
+
"table.contributors": "贡献者",
|
|
56
64
|
"table.codeFiles": "代码文件",
|
|
57
65
|
"table.additions": "新增行数",
|
|
58
66
|
"table.deletions": "删除行数",
|
|
@@ -68,5 +76,76 @@
|
|
|
68
76
|
"markdown.branches": "**分支**:{{branches}}\n\n",
|
|
69
77
|
"markdown.totalCommits": "**总提交数**:{{count}}\n\n",
|
|
70
78
|
"markdown.statistics": "## 统计数据\n\n",
|
|
71
|
-
"markdown.proposalDetails": "\n## 提案详情\n\n"
|
|
79
|
+
"markdown.proposalDetails": "\n## 提案详情\n\n",
|
|
80
|
+
|
|
81
|
+
"multi.beta.warning": "⚠️ 测试版:多仓库模式为实验性功能",
|
|
82
|
+
"multi.beta.feedback": " 请反馈问题至:https://github.com/Orchardxyz/openspec-stat/issues",
|
|
83
|
+
"multi.loading.config": "🔍 正在加载多仓库配置...",
|
|
84
|
+
"multi.repo.cloning": "☁️ 正在克隆 {{repo}}...",
|
|
85
|
+
"multi.repo.cloned": "✅ 成功克隆 {{repo}}",
|
|
86
|
+
"multi.repo.fetching": "🔄 正在拉取 {{repo}} 的远程分支...",
|
|
87
|
+
"multi.repo.analyzing": "📊 正在分析 {{repo}} ({{type}})...",
|
|
88
|
+
"multi.repo.completed": "✅ 完成 {{repo}}:{{commits}} 次提交",
|
|
89
|
+
"multi.repo.failed": "❌ 失败 {{repo}}:{{error}}",
|
|
90
|
+
"multi.repo.skipped": "⏭️ 跳过 {{repo}}:已禁用",
|
|
91
|
+
"multi.cleanup.start": "🧹 正在清理临时目录...",
|
|
92
|
+
"multi.cleanup.done": "✅ 清理完成",
|
|
93
|
+
"multi.summary.title": "\n📦 多仓库汇总\n",
|
|
94
|
+
"multi.summary.repos": "仓库:{{total}} 个({{success}} 成功,{{failed}} 失败)",
|
|
95
|
+
"multi.progress.batch": "正在处理批次 {{current}}/{{total}}...",
|
|
96
|
+
"multi.table.repository": "仓库",
|
|
97
|
+
"multi.table.type": "类型",
|
|
98
|
+
|
|
99
|
+
"init.welcome": "\n📋 OpenSpec 配置向导\n",
|
|
100
|
+
"init.welcomeMulti": "\n📋 OpenSpec 多仓库配置向导(测试版)\n",
|
|
101
|
+
"init.configName": "配置文件名:",
|
|
102
|
+
"init.addRepository": "\n📦 仓库 {{number}}",
|
|
103
|
+
"init.repoType": "仓库类型:",
|
|
104
|
+
"init.repoType.local": "📁 本地 - 我的机器上已有此仓库",
|
|
105
|
+
"init.repoType.remote": "☁️ 远程 - 从远程 URL 克隆",
|
|
106
|
+
"init.repoName": "仓库名称(用于显示):",
|
|
107
|
+
"init.repoPath": "本地路径(绝对或相对路径):",
|
|
108
|
+
"init.repoUrl": "Git URL(例如:git@github.com:org/repo.git):",
|
|
109
|
+
"init.repoUrlInvalid": "无效的 Git URL 格式",
|
|
110
|
+
"init.useFullClone": "使用完整克隆(推荐以确保准确性)?",
|
|
111
|
+
"init.cloneDepth": "克隆深度(提交数量):",
|
|
112
|
+
"init.branches": "要分析的分支(逗号分隔):",
|
|
113
|
+
"init.addMore": "添加另一个仓库?",
|
|
114
|
+
"init.timeConfig": "\n⏰ 时间范围配置",
|
|
115
|
+
"init.useDefaultTime": "使用默认时间范围(昨天 20:00 - 今天 20:00)?",
|
|
116
|
+
"init.sinceHours": "开始时间偏移(小时,负数表示过去):",
|
|
117
|
+
"init.untilHours": "结束时间(一天中的小时,0-23):",
|
|
118
|
+
"init.advanced": "\n⚙️ 高级选项",
|
|
119
|
+
"init.configureAdvanced": "配置高级选项?",
|
|
120
|
+
"init.openspecDir": "OpenSpec 目录路径:",
|
|
121
|
+
"init.maxConcurrent": "最大并发仓库操作数:",
|
|
122
|
+
"init.authorMapping": "配置作者名称映射?",
|
|
123
|
+
"init.authorMappingInfo": "\n作者映射帮助统一同一人的多个 Git 身份。\n",
|
|
124
|
+
"init.gitIdentity": "Git 身份(名称或邮箱):",
|
|
125
|
+
"init.unifiedName": "\"{{identity}}\" 的统一名称:",
|
|
126
|
+
"init.addMoreMapping": "添加另一个映射?",
|
|
127
|
+
"init.preview": "\n✅ 配置预览:\n",
|
|
128
|
+
"init.save": "\n保存此配置?",
|
|
129
|
+
"init.saved": "\n✅ 配置已保存至 {{path}}",
|
|
130
|
+
"init.runCommand": "\n运行:openspec-stat multi -c {{path}}",
|
|
131
|
+
"init.templateCreated": "✅ 模板已创建于 {{path}}",
|
|
132
|
+
"init.templateEdit": "请编辑文件并配置您的仓库。",
|
|
133
|
+
|
|
134
|
+
"config.validation.noRepos": "无效配置:\"repositories\" 必须是数组",
|
|
135
|
+
"config.validation.emptyRepos": "无效配置:至少需要一个仓库",
|
|
136
|
+
"config.validation.noName": "仓库 {{index}}:需要 \"name\"",
|
|
137
|
+
"config.validation.invalidType": "仓库 \"{{name}}\":\"type\" 必须是 \"local\" 或 \"remote\"",
|
|
138
|
+
"config.validation.noPath": "仓库 \"{{name}}\":本地类型需要 \"path\"",
|
|
139
|
+
"config.validation.noUrl": "仓库 \"{{name}}\":远程类型需要 \"url\"",
|
|
140
|
+
"config.validation.noBranches": "仓库 \"{{name}}\":至少需要一个分支",
|
|
141
|
+
"config.summary.title": "\n📋 配置摘要\n",
|
|
142
|
+
"config.summary.repositories": "仓库:",
|
|
143
|
+
"config.summary.timeRange": "\n时间范围:",
|
|
144
|
+
"config.summary.since": " 开始:{{hours}} 小时",
|
|
145
|
+
"config.summary.until": " 结束:{{hours}}:00",
|
|
146
|
+
"config.summary.parallelism": "\n并发:",
|
|
147
|
+
"config.summary.maxConcurrent": " 最大并发数:{{count}}",
|
|
148
|
+
"config.summary.remoteCache": "\n远程缓存:",
|
|
149
|
+
"config.summary.cacheDir": " 目录:{{dir}}",
|
|
150
|
+
"config.summary.autoCleanup": " 自动清理:{{enabled}}"
|
|
72
151
|
}
|
|
@@ -0,0 +1,130 @@
|
|
|
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/multi/config-validator.ts
|
|
30
|
+
var config_validator_exports = {};
|
|
31
|
+
__export(config_validator_exports, {
|
|
32
|
+
printConfigSummary: () => printConfigSummary,
|
|
33
|
+
validateAndFillDefaults: () => validateAndFillDefaults
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(config_validator_exports);
|
|
36
|
+
var import_chalk = __toESM(require("chalk"));
|
|
37
|
+
var import_i18n = require("../i18n/index.js");
|
|
38
|
+
var DEFAULT_MULTI_REPO_CONFIG = {
|
|
39
|
+
mode: "multi-repo",
|
|
40
|
+
defaultSinceHours: -30,
|
|
41
|
+
defaultUntilHours: 20,
|
|
42
|
+
openspecDir: "openspec/",
|
|
43
|
+
excludeExtensions: [".md", ".txt", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico", ".webp"],
|
|
44
|
+
activeUserWeeks: 2,
|
|
45
|
+
authorMapping: {},
|
|
46
|
+
parallelism: {
|
|
47
|
+
maxConcurrent: 3,
|
|
48
|
+
timeout: 6e5
|
|
49
|
+
},
|
|
50
|
+
remoteCache: {
|
|
51
|
+
dir: "/tmp/openspec-stat-cache",
|
|
52
|
+
autoCleanup: true,
|
|
53
|
+
cleanupOnComplete: true,
|
|
54
|
+
cleanupOnError: true
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
function validateAndFillDefaults(config) {
|
|
58
|
+
if (!config.repositories || !Array.isArray(config.repositories)) {
|
|
59
|
+
throw new Error((0, import_i18n.t)("config.validation.noRepos"));
|
|
60
|
+
}
|
|
61
|
+
if (config.repositories.length === 0) {
|
|
62
|
+
throw new Error((0, import_i18n.t)("config.validation.emptyRepos"));
|
|
63
|
+
}
|
|
64
|
+
config.repositories.forEach((repo, index) => {
|
|
65
|
+
if (!repo.name) {
|
|
66
|
+
throw new Error((0, import_i18n.t)("config.validation.noName", { index: String(index + 1) }));
|
|
67
|
+
}
|
|
68
|
+
if (!repo.type || !["local", "remote"].includes(repo.type)) {
|
|
69
|
+
throw new Error((0, import_i18n.t)("config.validation.invalidType", { name: repo.name }));
|
|
70
|
+
}
|
|
71
|
+
if (repo.type === "local" && !repo.path) {
|
|
72
|
+
throw new Error((0, import_i18n.t)("config.validation.noPath", { name: repo.name }));
|
|
73
|
+
}
|
|
74
|
+
if (repo.type === "remote" && !repo.url) {
|
|
75
|
+
throw new Error((0, import_i18n.t)("config.validation.noUrl", { name: repo.name }));
|
|
76
|
+
}
|
|
77
|
+
if (!repo.branches || !Array.isArray(repo.branches) || repo.branches.length === 0) {
|
|
78
|
+
throw new Error((0, import_i18n.t)("config.validation.noBranches", { name: repo.name }));
|
|
79
|
+
}
|
|
80
|
+
if (repo.enabled === void 0) {
|
|
81
|
+
repo.enabled = true;
|
|
82
|
+
}
|
|
83
|
+
if (repo.type === "remote" && !repo.cloneOptions) {
|
|
84
|
+
repo.cloneOptions = { depth: null, singleBranch: false };
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
const mergedConfig = {
|
|
88
|
+
...DEFAULT_MULTI_REPO_CONFIG,
|
|
89
|
+
...config,
|
|
90
|
+
parallelism: {
|
|
91
|
+
...DEFAULT_MULTI_REPO_CONFIG.parallelism,
|
|
92
|
+
...config.parallelism
|
|
93
|
+
},
|
|
94
|
+
remoteCache: {
|
|
95
|
+
...DEFAULT_MULTI_REPO_CONFIG.remoteCache,
|
|
96
|
+
...config.remoteCache
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
return mergedConfig;
|
|
100
|
+
}
|
|
101
|
+
function printConfigSummary(config) {
|
|
102
|
+
var _a, _b, _c, _d;
|
|
103
|
+
console.log(import_chalk.default.blue((0, import_i18n.t)("config.summary.title")));
|
|
104
|
+
console.log(import_chalk.default.cyan((0, import_i18n.t)("config.summary.repositories")));
|
|
105
|
+
(_a = config.repositories) == null ? void 0 : _a.forEach((repo, i) => {
|
|
106
|
+
const icon = repo.type === "local" ? "📁" : "☁️";
|
|
107
|
+
const location = repo.type === "local" ? repo.path : repo.url;
|
|
108
|
+
console.log(` ${i + 1}. ${icon} ${import_chalk.default.bold(repo.name)} (${repo.type})`);
|
|
109
|
+
console.log(` ${import_chalk.default.gray(location)}`);
|
|
110
|
+
console.log(` ${import_chalk.default.gray("Branches:")} ${repo.branches.join(", ")}`);
|
|
111
|
+
});
|
|
112
|
+
console.log(import_chalk.default.cyan((0, import_i18n.t)("config.summary.timeRange")));
|
|
113
|
+
console.log((0, import_i18n.t)("config.summary.since", { hours: String(config.defaultSinceHours) }));
|
|
114
|
+
console.log((0, import_i18n.t)("config.summary.until", { hours: String(config.defaultUntilHours) }));
|
|
115
|
+
console.log(import_chalk.default.cyan((0, import_i18n.t)("config.summary.parallelism")));
|
|
116
|
+
console.log((0, import_i18n.t)("config.summary.maxConcurrent", { count: String(((_b = config.parallelism) == null ? void 0 : _b.maxConcurrent) || 3) }));
|
|
117
|
+
console.log(import_chalk.default.cyan((0, import_i18n.t)("config.summary.remoteCache")));
|
|
118
|
+
console.log((0, import_i18n.t)("config.summary.cacheDir", { dir: ((_c = config.remoteCache) == null ? void 0 : _c.dir) || "/tmp/openspec-stat-cache" }));
|
|
119
|
+
console.log(
|
|
120
|
+
(0, import_i18n.t)("config.summary.autoCleanup", {
|
|
121
|
+
enabled: ((_d = config.remoteCache) == null ? void 0 : _d.cleanupOnComplete) ? "Yes" : "No"
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
console.log();
|
|
125
|
+
}
|
|
126
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
127
|
+
0 && (module.exports = {
|
|
128
|
+
printConfigSummary,
|
|
129
|
+
validateAndFillDefaults
|
|
130
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export declare function runConfigWizard(isMultiRepo?: boolean): Promise<void>;
|
|
2
|
+
export declare const SINGLE_REPO_TEMPLATE: {
|
|
3
|
+
defaultBranches: string[];
|
|
4
|
+
defaultSinceHours: number;
|
|
5
|
+
defaultUntilHours: number;
|
|
6
|
+
authorMapping: {
|
|
7
|
+
'user@email1.com': string;
|
|
8
|
+
'user@email2.com': string;
|
|
9
|
+
};
|
|
10
|
+
openspecDir: string;
|
|
11
|
+
excludeExtensions: string[];
|
|
12
|
+
activeUserWeeks: number;
|
|
13
|
+
};
|
|
14
|
+
export declare const MULTI_REPO_TEMPLATE: {
|
|
15
|
+
mode: string;
|
|
16
|
+
repositories: ({
|
|
17
|
+
name: string;
|
|
18
|
+
type: string;
|
|
19
|
+
path: string;
|
|
20
|
+
branches: string[];
|
|
21
|
+
url?: undefined;
|
|
22
|
+
cloneOptions?: undefined;
|
|
23
|
+
} | {
|
|
24
|
+
name: string;
|
|
25
|
+
type: string;
|
|
26
|
+
url: string;
|
|
27
|
+
branches: string[];
|
|
28
|
+
cloneOptions: {
|
|
29
|
+
depth: null;
|
|
30
|
+
singleBranch: boolean;
|
|
31
|
+
};
|
|
32
|
+
path?: undefined;
|
|
33
|
+
})[];
|
|
34
|
+
defaultSinceHours: number;
|
|
35
|
+
defaultUntilHours: number;
|
|
36
|
+
authorMapping: {};
|
|
37
|
+
openspecDir: string;
|
|
38
|
+
excludeExtensions: string[];
|
|
39
|
+
activeUserWeeks: number;
|
|
40
|
+
parallelism: {
|
|
41
|
+
maxConcurrent: number;
|
|
42
|
+
timeout: number;
|
|
43
|
+
};
|
|
44
|
+
remoteCache: {
|
|
45
|
+
dir: string;
|
|
46
|
+
autoCleanup: boolean;
|
|
47
|
+
cleanupOnComplete: boolean;
|
|
48
|
+
cleanupOnError: boolean;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
@@ -0,0 +1,331 @@
|
|
|
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/multi/config-wizard.ts
|
|
30
|
+
var config_wizard_exports = {};
|
|
31
|
+
__export(config_wizard_exports, {
|
|
32
|
+
MULTI_REPO_TEMPLATE: () => MULTI_REPO_TEMPLATE,
|
|
33
|
+
SINGLE_REPO_TEMPLATE: () => SINGLE_REPO_TEMPLATE,
|
|
34
|
+
runConfigWizard: () => runConfigWizard
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(config_wizard_exports);
|
|
37
|
+
var import_prompts = require("@inquirer/prompts");
|
|
38
|
+
var import_chalk = __toESM(require("chalk"));
|
|
39
|
+
var import_fs = require("fs");
|
|
40
|
+
var import_i18n = require("../i18n/index.js");
|
|
41
|
+
async function runConfigWizard(isMultiRepo = false) {
|
|
42
|
+
console.log(import_chalk.default.blue.bold(isMultiRepo ? (0, import_i18n.t)("init.welcomeMulti") : (0, import_i18n.t)("init.welcome")));
|
|
43
|
+
const configName = await (0, import_prompts.input)({
|
|
44
|
+
message: (0, import_i18n.t)("init.configName"),
|
|
45
|
+
default: isMultiRepo ? ".openspec-stats.multi.json" : ".openspec-stats.json"
|
|
46
|
+
});
|
|
47
|
+
if (!isMultiRepo) {
|
|
48
|
+
const singleRepoConfig = await createSingleRepoConfig();
|
|
49
|
+
saveConfig(configName, singleRepoConfig);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const repositories = [];
|
|
53
|
+
let addMore = true;
|
|
54
|
+
while (addMore) {
|
|
55
|
+
console.log(import_chalk.default.cyan((0, import_i18n.t)("init.addRepository", { number: String(repositories.length + 1) })));
|
|
56
|
+
const repo = await addRepository();
|
|
57
|
+
repositories.push(repo);
|
|
58
|
+
addMore = await (0, import_prompts.confirm)({
|
|
59
|
+
message: (0, import_i18n.t)("init.addMore"),
|
|
60
|
+
default: false
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
console.log(import_chalk.default.cyan((0, import_i18n.t)("init.timeConfig")));
|
|
64
|
+
const timeConfig = await configureTimeRange();
|
|
65
|
+
console.log(import_chalk.default.cyan((0, import_i18n.t)("init.advanced")));
|
|
66
|
+
const advancedConfig = await configureAdvanced();
|
|
67
|
+
const config = {
|
|
68
|
+
mode: "multi-repo",
|
|
69
|
+
repositories,
|
|
70
|
+
...timeConfig,
|
|
71
|
+
...advancedConfig
|
|
72
|
+
};
|
|
73
|
+
console.log(import_chalk.default.green((0, import_i18n.t)("init.preview")));
|
|
74
|
+
console.log(JSON.stringify(config, null, 2));
|
|
75
|
+
const confirmed = await (0, import_prompts.confirm)({
|
|
76
|
+
message: (0, import_i18n.t)("init.save"),
|
|
77
|
+
default: true
|
|
78
|
+
});
|
|
79
|
+
if (confirmed) {
|
|
80
|
+
saveConfig(configName, config);
|
|
81
|
+
console.log(import_chalk.default.green((0, import_i18n.t)("init.saved", { path: configName })));
|
|
82
|
+
console.log(import_chalk.default.blue((0, import_i18n.t)("init.runCommand", { path: configName })));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function createSingleRepoConfig() {
|
|
86
|
+
console.log(import_chalk.default.cyan((0, import_i18n.t)("init.timeConfig")));
|
|
87
|
+
const timeConfig = await configureTimeRange();
|
|
88
|
+
const branches = await (0, import_prompts.input)({
|
|
89
|
+
message: (0, import_i18n.t)("init.branches"),
|
|
90
|
+
default: "origin/master"
|
|
91
|
+
});
|
|
92
|
+
const advancedConfig = await configureAdvanced();
|
|
93
|
+
return {
|
|
94
|
+
defaultBranches: branches.split(",").map((b) => b.trim()).filter(Boolean),
|
|
95
|
+
...timeConfig,
|
|
96
|
+
...advancedConfig
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async function addRepository() {
|
|
100
|
+
const type = await (0, import_prompts.select)({
|
|
101
|
+
message: (0, import_i18n.t)("init.repoType"),
|
|
102
|
+
choices: [
|
|
103
|
+
{
|
|
104
|
+
name: (0, import_i18n.t)("init.repoType.local"),
|
|
105
|
+
value: "local"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: (0, import_i18n.t)("init.repoType.remote"),
|
|
109
|
+
value: "remote"
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
});
|
|
113
|
+
const name = await (0, import_prompts.input)({
|
|
114
|
+
message: (0, import_i18n.t)("init.repoName"),
|
|
115
|
+
validate: (value) => value.length > 0 || "Name is required"
|
|
116
|
+
});
|
|
117
|
+
const repo = {
|
|
118
|
+
name,
|
|
119
|
+
type,
|
|
120
|
+
branches: []
|
|
121
|
+
};
|
|
122
|
+
if (type === "local") {
|
|
123
|
+
repo.path = await (0, import_prompts.input)({
|
|
124
|
+
message: (0, import_i18n.t)("init.repoPath"),
|
|
125
|
+
default: ".",
|
|
126
|
+
validate: (value) => value.length > 0 || "Path is required"
|
|
127
|
+
});
|
|
128
|
+
} else {
|
|
129
|
+
repo.url = await (0, import_prompts.input)({
|
|
130
|
+
message: (0, import_i18n.t)("init.repoUrl"),
|
|
131
|
+
validate: (value) => {
|
|
132
|
+
if (!value.length)
|
|
133
|
+
return "URL is required";
|
|
134
|
+
if (!value.match(/^(git@|https:\/\/)/)) {
|
|
135
|
+
return (0, import_i18n.t)("init.repoUrlInvalid");
|
|
136
|
+
}
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
const useFullClone = await (0, import_prompts.confirm)({
|
|
141
|
+
message: (0, import_i18n.t)("init.useFullClone"),
|
|
142
|
+
default: true
|
|
143
|
+
});
|
|
144
|
+
if (!useFullClone) {
|
|
145
|
+
const depth = await (0, import_prompts.input)({
|
|
146
|
+
message: (0, import_i18n.t)("init.cloneDepth"),
|
|
147
|
+
default: "100",
|
|
148
|
+
validate: (value) => !isNaN(Number(value)) || "Must be a number"
|
|
149
|
+
});
|
|
150
|
+
repo.cloneOptions = { depth: Number(depth) };
|
|
151
|
+
} else {
|
|
152
|
+
repo.cloneOptions = { depth: null };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const branchInput = await (0, import_prompts.input)({
|
|
156
|
+
message: (0, import_i18n.t)("init.branches"),
|
|
157
|
+
default: "origin/master"
|
|
158
|
+
});
|
|
159
|
+
repo.branches = branchInput.split(",").map((b) => b.trim()).filter(Boolean);
|
|
160
|
+
return repo;
|
|
161
|
+
}
|
|
162
|
+
async function configureTimeRange() {
|
|
163
|
+
const useDefault = await (0, import_prompts.confirm)({
|
|
164
|
+
message: (0, import_i18n.t)("init.useDefaultTime"),
|
|
165
|
+
default: true
|
|
166
|
+
});
|
|
167
|
+
if (useDefault) {
|
|
168
|
+
return {
|
|
169
|
+
defaultSinceHours: -30,
|
|
170
|
+
defaultUntilHours: 20
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const sinceHours = await (0, import_prompts.input)({
|
|
174
|
+
message: (0, import_i18n.t)("init.sinceHours"),
|
|
175
|
+
default: "-30",
|
|
176
|
+
validate: (value) => !isNaN(Number(value)) || "Must be a number"
|
|
177
|
+
});
|
|
178
|
+
const untilHours = await (0, import_prompts.input)({
|
|
179
|
+
message: (0, import_i18n.t)("init.untilHours"),
|
|
180
|
+
default: "20",
|
|
181
|
+
validate: (value) => {
|
|
182
|
+
const num = Number(value);
|
|
183
|
+
return !isNaN(num) && num >= 0 && num <= 23 || "Must be 0-23";
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
return {
|
|
187
|
+
defaultSinceHours: Number(sinceHours),
|
|
188
|
+
defaultUntilHours: Number(untilHours)
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
async function configureAdvanced() {
|
|
192
|
+
const configureAdvanced2 = await (0, import_prompts.confirm)({
|
|
193
|
+
message: (0, import_i18n.t)("init.configureAdvanced"),
|
|
194
|
+
default: false
|
|
195
|
+
});
|
|
196
|
+
if (!configureAdvanced2) {
|
|
197
|
+
return getDefaultAdvancedConfig();
|
|
198
|
+
}
|
|
199
|
+
const config = {};
|
|
200
|
+
config.openspecDir = await (0, import_prompts.input)({
|
|
201
|
+
message: (0, import_i18n.t)("init.openspecDir"),
|
|
202
|
+
default: "openspec/"
|
|
203
|
+
});
|
|
204
|
+
const maxConcurrent = await (0, import_prompts.input)({
|
|
205
|
+
message: (0, import_i18n.t)("init.maxConcurrent"),
|
|
206
|
+
default: "3",
|
|
207
|
+
validate: (value) => {
|
|
208
|
+
const num = Number(value);
|
|
209
|
+
return !isNaN(num) && num > 0 || "Must be positive number";
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
config.parallelism = {
|
|
213
|
+
maxConcurrent: Number(maxConcurrent),
|
|
214
|
+
timeout: 6e5
|
|
215
|
+
};
|
|
216
|
+
config.remoteCache = {
|
|
217
|
+
dir: "/tmp/openspec-stat-cache",
|
|
218
|
+
autoCleanup: true,
|
|
219
|
+
cleanupOnComplete: true,
|
|
220
|
+
cleanupOnError: true
|
|
221
|
+
};
|
|
222
|
+
config.excludeExtensions = [".md", ".txt", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico", ".webp"];
|
|
223
|
+
config.activeUserWeeks = 2;
|
|
224
|
+
const addAuthorMapping = await (0, import_prompts.confirm)({
|
|
225
|
+
message: (0, import_i18n.t)("init.authorMapping"),
|
|
226
|
+
default: false
|
|
227
|
+
});
|
|
228
|
+
if (addAuthorMapping) {
|
|
229
|
+
config.authorMapping = await configureAuthorMapping();
|
|
230
|
+
} else {
|
|
231
|
+
config.authorMapping = {};
|
|
232
|
+
}
|
|
233
|
+
return config;
|
|
234
|
+
}
|
|
235
|
+
async function configureAuthorMapping() {
|
|
236
|
+
console.log(import_chalk.default.gray((0, import_i18n.t)("init.authorMappingInfo")));
|
|
237
|
+
const mapping = {};
|
|
238
|
+
let addMore = true;
|
|
239
|
+
while (addMore) {
|
|
240
|
+
const gitIdentity = await (0, import_prompts.input)({
|
|
241
|
+
message: (0, import_i18n.t)("init.gitIdentity")
|
|
242
|
+
});
|
|
243
|
+
if (!gitIdentity)
|
|
244
|
+
break;
|
|
245
|
+
const unifiedName = await (0, import_prompts.input)({
|
|
246
|
+
message: (0, import_i18n.t)("init.unifiedName", { identity: gitIdentity })
|
|
247
|
+
});
|
|
248
|
+
mapping[gitIdentity] = unifiedName;
|
|
249
|
+
addMore = await (0, import_prompts.confirm)({
|
|
250
|
+
message: (0, import_i18n.t)("init.addMoreMapping"),
|
|
251
|
+
default: false
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
return mapping;
|
|
255
|
+
}
|
|
256
|
+
function getDefaultAdvancedConfig() {
|
|
257
|
+
return {
|
|
258
|
+
openspecDir: "openspec/",
|
|
259
|
+
excludeExtensions: [".md", ".txt", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico", ".webp"],
|
|
260
|
+
activeUserWeeks: 2,
|
|
261
|
+
authorMapping: {},
|
|
262
|
+
parallelism: {
|
|
263
|
+
maxConcurrent: 3,
|
|
264
|
+
timeout: 6e5
|
|
265
|
+
},
|
|
266
|
+
remoteCache: {
|
|
267
|
+
dir: "/tmp/openspec-stat-cache",
|
|
268
|
+
autoCleanup: true,
|
|
269
|
+
cleanupOnComplete: true,
|
|
270
|
+
cleanupOnError: true
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function saveConfig(path, config) {
|
|
275
|
+
(0, import_fs.writeFileSync)(path, JSON.stringify(config, null, 2));
|
|
276
|
+
}
|
|
277
|
+
var SINGLE_REPO_TEMPLATE = {
|
|
278
|
+
defaultBranches: ["origin/master"],
|
|
279
|
+
defaultSinceHours: -30,
|
|
280
|
+
defaultUntilHours: 20,
|
|
281
|
+
authorMapping: {
|
|
282
|
+
"user@email1.com": "User Name",
|
|
283
|
+
"user@email2.com": "User Name"
|
|
284
|
+
},
|
|
285
|
+
openspecDir: "openspec/",
|
|
286
|
+
excludeExtensions: [".md", ".txt", ".png", ".jpg"],
|
|
287
|
+
activeUserWeeks: 2
|
|
288
|
+
};
|
|
289
|
+
var MULTI_REPO_TEMPLATE = {
|
|
290
|
+
mode: "multi-repo",
|
|
291
|
+
repositories: [
|
|
292
|
+
{
|
|
293
|
+
name: "example-local-repo",
|
|
294
|
+
type: "local",
|
|
295
|
+
path: "./path/to/repo",
|
|
296
|
+
branches: ["origin/master"]
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: "example-remote-repo",
|
|
300
|
+
type: "remote",
|
|
301
|
+
url: "git@github.com:org/repo.git",
|
|
302
|
+
branches: ["origin/main"],
|
|
303
|
+
cloneOptions: {
|
|
304
|
+
depth: null,
|
|
305
|
+
singleBranch: false
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
],
|
|
309
|
+
defaultSinceHours: -30,
|
|
310
|
+
defaultUntilHours: 20,
|
|
311
|
+
authorMapping: {},
|
|
312
|
+
openspecDir: "openspec/",
|
|
313
|
+
excludeExtensions: [".md", ".txt", ".png", ".jpg"],
|
|
314
|
+
activeUserWeeks: 2,
|
|
315
|
+
parallelism: {
|
|
316
|
+
maxConcurrent: 3,
|
|
317
|
+
timeout: 6e5
|
|
318
|
+
},
|
|
319
|
+
remoteCache: {
|
|
320
|
+
dir: "/tmp/openspec-stat-cache",
|
|
321
|
+
autoCleanup: true,
|
|
322
|
+
cleanupOnComplete: true,
|
|
323
|
+
cleanupOnError: true
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
327
|
+
0 && (module.exports = {
|
|
328
|
+
MULTI_REPO_TEMPLATE,
|
|
329
|
+
SINGLE_REPO_TEMPLATE,
|
|
330
|
+
runConfigWizard
|
|
331
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { MultiRepoConfig, RepositoryResult } from '../types.js';
|
|
2
|
+
export declare class MultiRepoAnalyzer {
|
|
3
|
+
private config;
|
|
4
|
+
private tempDirs;
|
|
5
|
+
constructor(config: MultiRepoConfig);
|
|
6
|
+
analyzeAll(since: Date, until: Date): Promise<RepositoryResult[]>;
|
|
7
|
+
private analyzeRepository;
|
|
8
|
+
private cloneRemoteRepository;
|
|
9
|
+
private resolveLocalPath;
|
|
10
|
+
private processInBatches;
|
|
11
|
+
private withTimeout;
|
|
12
|
+
cleanupTempDirs(): Promise<void>;
|
|
13
|
+
registerCleanupHandlers(): void;
|
|
14
|
+
}
|