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
|
@@ -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
|
}
|
package/dist/cjs/formatters.js
CHANGED
|
@@ -36,7 +36,7 @@ var import_cli_table3 = __toESM(require("cli-table3"));
|
|
|
36
36
|
var import_chalk = __toESM(require("chalk"));
|
|
37
37
|
var import_i18n = require("./i18n/index.js");
|
|
38
38
|
var OutputFormatter = class {
|
|
39
|
-
formatTable(result, verbose = false) {
|
|
39
|
+
formatTable(result, verbose = false, showContributors = true) {
|
|
40
40
|
let output = "";
|
|
41
41
|
output += import_chalk.default.bold((0, import_i18n.t)("output.title"));
|
|
42
42
|
output += import_chalk.default.gray(
|
|
@@ -47,7 +47,97 @@ var OutputFormatter = class {
|
|
|
47
47
|
);
|
|
48
48
|
output += import_chalk.default.gray((0, import_i18n.t)("output.branches", { branches: result.branches.join(", ") }));
|
|
49
49
|
output += import_chalk.default.gray((0, import_i18n.t)("output.totalCommits", { count: String(result.totalCommits) }));
|
|
50
|
+
if (result.proposals.size > 0) {
|
|
51
|
+
output += import_chalk.default.bold.magenta(`
|
|
52
|
+
${(0, import_i18n.t)("output.proposalSummary")}
|
|
53
|
+
`);
|
|
54
|
+
const proposalTable = new import_cli_table3.default({
|
|
55
|
+
head: [
|
|
56
|
+
import_chalk.default.magenta((0, import_i18n.t)("table.proposal")),
|
|
57
|
+
import_chalk.default.magenta((0, import_i18n.t)("table.commits")),
|
|
58
|
+
import_chalk.default.magenta((0, import_i18n.t)("table.contributors")),
|
|
59
|
+
import_chalk.default.magenta((0, import_i18n.t)("table.codeFiles")),
|
|
60
|
+
import_chalk.default.magenta((0, import_i18n.t)("table.additions")),
|
|
61
|
+
import_chalk.default.magenta((0, import_i18n.t)("table.deletions")),
|
|
62
|
+
import_chalk.default.magenta((0, import_i18n.t)("table.netChanges"))
|
|
63
|
+
],
|
|
64
|
+
style: {
|
|
65
|
+
head: [],
|
|
66
|
+
border: []
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
const sortedProposals = Array.from(result.proposals.values()).sort((a, b) => b.netChanges - a.netChanges);
|
|
70
|
+
for (const proposalStats of sortedProposals) {
|
|
71
|
+
const contributors = Array.from(proposalStats.contributors).join(", ");
|
|
72
|
+
proposalTable.push([
|
|
73
|
+
proposalStats.proposal,
|
|
74
|
+
proposalStats.commits.toString(),
|
|
75
|
+
contributors,
|
|
76
|
+
proposalStats.codeFilesChanged.toString(),
|
|
77
|
+
import_chalk.default.green(`+${proposalStats.additions}`),
|
|
78
|
+
import_chalk.default.red(`-${proposalStats.deletions}`),
|
|
79
|
+
proposalStats.netChanges >= 0 ? import_chalk.default.green(`+${proposalStats.netChanges}`) : import_chalk.default.red(`${proposalStats.netChanges}`)
|
|
80
|
+
]);
|
|
81
|
+
}
|
|
82
|
+
output += proposalTable.toString() + "\n";
|
|
83
|
+
const totalProposals = result.proposals.size;
|
|
84
|
+
const totalProposalCommits = Array.from(result.proposals.values()).reduce((sum, p) => sum + p.commits, 0);
|
|
85
|
+
const totalProposalFiles = Array.from(result.proposals.values()).reduce((sum, p) => sum + p.codeFilesChanged, 0);
|
|
86
|
+
const totalProposalAdditions = Array.from(result.proposals.values()).reduce((sum, p) => sum + p.additions, 0);
|
|
87
|
+
const totalProposalDeletions = Array.from(result.proposals.values()).reduce((sum, p) => sum + p.deletions, 0);
|
|
88
|
+
const totalProposalNetChanges = Array.from(result.proposals.values()).reduce((sum, p) => sum + p.netChanges, 0);
|
|
89
|
+
output += import_chalk.default.gray(
|
|
90
|
+
(0, import_i18n.t)("output.proposalTotal", {
|
|
91
|
+
count: totalProposals.toString(),
|
|
92
|
+
commits: totalProposalCommits.toString(),
|
|
93
|
+
files: totalProposalFiles.toString(),
|
|
94
|
+
additions: totalProposalAdditions.toString(),
|
|
95
|
+
deletions: totalProposalDeletions.toString(),
|
|
96
|
+
netChanges: totalProposalNetChanges.toString()
|
|
97
|
+
})
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
output += import_chalk.default.bold.cyan(`
|
|
101
|
+
${(0, import_i18n.t)("output.authorSummary")}
|
|
102
|
+
`);
|
|
50
103
|
const sortedAuthors = Array.from(result.authors.values()).sort((a, b) => b.commits - a.commits);
|
|
104
|
+
if (!showContributors) {
|
|
105
|
+
const totalAuthors = sortedAuthors.length;
|
|
106
|
+
const totalCommits = sortedAuthors.reduce((sum, s) => sum + s.commits, 0);
|
|
107
|
+
const totalProposalsSet = /* @__PURE__ */ new Set();
|
|
108
|
+
sortedAuthors.forEach((s) => s.openspecProposals.forEach((p) => totalProposalsSet.add(p)));
|
|
109
|
+
const totalFiles = sortedAuthors.reduce((sum, s) => sum + s.codeFilesChanged, 0);
|
|
110
|
+
const totalAdditions = sortedAuthors.reduce((sum, s) => sum + s.additions, 0);
|
|
111
|
+
const totalDeletions = sortedAuthors.reduce((sum, s) => sum + s.deletions, 0);
|
|
112
|
+
const totalNetChanges = sortedAuthors.reduce((sum, s) => sum + s.netChanges, 0);
|
|
113
|
+
const summaryTable = new import_cli_table3.default({
|
|
114
|
+
head: [
|
|
115
|
+
import_chalk.default.cyan((0, import_i18n.t)("table.contributors")),
|
|
116
|
+
import_chalk.default.cyan((0, import_i18n.t)("table.commits")),
|
|
117
|
+
import_chalk.default.cyan((0, import_i18n.t)("table.proposals")),
|
|
118
|
+
import_chalk.default.cyan((0, import_i18n.t)("table.codeFiles")),
|
|
119
|
+
import_chalk.default.cyan((0, import_i18n.t)("table.additions")),
|
|
120
|
+
import_chalk.default.cyan((0, import_i18n.t)("table.deletions")),
|
|
121
|
+
import_chalk.default.cyan((0, import_i18n.t)("table.netChanges"))
|
|
122
|
+
],
|
|
123
|
+
style: {
|
|
124
|
+
head: [],
|
|
125
|
+
border: []
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
summaryTable.push([
|
|
129
|
+
totalAuthors.toString(),
|
|
130
|
+
totalCommits.toString(),
|
|
131
|
+
totalProposalsSet.size.toString(),
|
|
132
|
+
totalFiles.toString(),
|
|
133
|
+
import_chalk.default.green(`+${totalAdditions}`),
|
|
134
|
+
import_chalk.default.red(`-${totalDeletions}`),
|
|
135
|
+
totalNetChanges >= 0 ? import_chalk.default.green(`+${totalNetChanges}`) : import_chalk.default.red(`${totalNetChanges}`)
|
|
136
|
+
]);
|
|
137
|
+
output += summaryTable.toString() + "\n";
|
|
138
|
+
output += import_chalk.default.gray((0, import_i18n.t)("output.contributorHint")) + "\n";
|
|
139
|
+
return output;
|
|
140
|
+
}
|
|
51
141
|
for (const stats of sortedAuthors) {
|
|
52
142
|
output += import_chalk.default.bold.cyan(`
|
|
53
143
|
${stats.author}
|
|
@@ -123,7 +213,8 @@ ${stats.author}
|
|
|
123
213
|
}
|
|
124
214
|
return output;
|
|
125
215
|
}
|
|
126
|
-
formatJSON(result) {
|
|
216
|
+
formatJSON(result, showContributors = true) {
|
|
217
|
+
const sortedAuthors = Array.from(result.authors.values());
|
|
127
218
|
const data = {
|
|
128
219
|
timeRange: {
|
|
129
220
|
since: result.timeRange.since.toISOString(),
|
|
@@ -131,7 +222,29 @@ ${stats.author}
|
|
|
131
222
|
},
|
|
132
223
|
branches: result.branches,
|
|
133
224
|
totalCommits: result.totalCommits,
|
|
134
|
-
|
|
225
|
+
proposals: {
|
|
226
|
+
items: Array.from(result.proposals.values()).map((stats) => ({
|
|
227
|
+
proposal: stats.proposal,
|
|
228
|
+
commits: stats.commits,
|
|
229
|
+
contributors: Array.from(stats.contributors),
|
|
230
|
+
contributorCount: stats.contributors.size,
|
|
231
|
+
codeFilesChanged: stats.codeFilesChanged,
|
|
232
|
+
additions: stats.additions,
|
|
233
|
+
deletions: stats.deletions,
|
|
234
|
+
netChanges: stats.netChanges
|
|
235
|
+
})),
|
|
236
|
+
summary: {
|
|
237
|
+
totalProposals: result.proposals.size,
|
|
238
|
+
totalCommits: Array.from(result.proposals.values()).reduce((sum, p) => sum + p.commits, 0),
|
|
239
|
+
totalCodeFiles: Array.from(result.proposals.values()).reduce((sum, p) => sum + p.codeFilesChanged, 0),
|
|
240
|
+
totalAdditions: Array.from(result.proposals.values()).reduce((sum, p) => sum + p.additions, 0),
|
|
241
|
+
totalDeletions: Array.from(result.proposals.values()).reduce((sum, p) => sum + p.deletions, 0),
|
|
242
|
+
totalNetChanges: Array.from(result.proposals.values()).reduce((sum, p) => sum + p.netChanges, 0)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
if (showContributors) {
|
|
247
|
+
data.authors = sortedAuthors.map((stats) => {
|
|
135
248
|
var _a;
|
|
136
249
|
return {
|
|
137
250
|
author: stats.author,
|
|
@@ -144,37 +257,106 @@ ${stats.author}
|
|
|
144
257
|
netChanges: stats.netChanges,
|
|
145
258
|
lastCommitDate: (_a = stats.lastCommitDate) == null ? void 0 : _a.toISOString()
|
|
146
259
|
};
|
|
147
|
-
})
|
|
148
|
-
}
|
|
260
|
+
});
|
|
261
|
+
} else {
|
|
262
|
+
const totalProposalsSet = /* @__PURE__ */ new Set();
|
|
263
|
+
sortedAuthors.forEach((s) => s.openspecProposals.forEach((p) => totalProposalsSet.add(p)));
|
|
264
|
+
data.authorsSummary = {
|
|
265
|
+
totalContributors: sortedAuthors.length,
|
|
266
|
+
totalCommits: sortedAuthors.reduce((sum, s) => sum + s.commits, 0),
|
|
267
|
+
totalProposals: totalProposalsSet.size,
|
|
268
|
+
totalCodeFiles: sortedAuthors.reduce((sum, s) => sum + s.codeFilesChanged, 0),
|
|
269
|
+
totalAdditions: sortedAuthors.reduce((sum, s) => sum + s.additions, 0),
|
|
270
|
+
totalDeletions: sortedAuthors.reduce((sum, s) => sum + s.deletions, 0),
|
|
271
|
+
totalNetChanges: sortedAuthors.reduce((sum, s) => sum + s.netChanges, 0)
|
|
272
|
+
};
|
|
273
|
+
}
|
|
149
274
|
return JSON.stringify(data, null, 2);
|
|
150
275
|
}
|
|
151
|
-
formatCSV(result) {
|
|
276
|
+
formatCSV(result, showContributors = true) {
|
|
152
277
|
var _a;
|
|
153
278
|
const rows = [];
|
|
279
|
+
rows.push(`
|
|
280
|
+
# ${(0, import_i18n.t)("output.proposalSummary")}`);
|
|
154
281
|
rows.push(
|
|
155
|
-
`${(0, import_i18n.t)("table.
|
|
282
|
+
`${(0, import_i18n.t)("table.proposal")},${(0, import_i18n.t)("table.commits")},${(0, import_i18n.t)("table.contributors")},${(0, import_i18n.t)("table.codeFiles")},${(0, import_i18n.t)("table.additions")},${(0, import_i18n.t)("table.deletions")},${(0, import_i18n.t)("table.netChanges")}`
|
|
156
283
|
);
|
|
157
|
-
const
|
|
158
|
-
for (const stats of
|
|
159
|
-
const
|
|
284
|
+
const sortedProposals = Array.from(result.proposals.values()).sort((a, b) => b.netChanges - a.netChanges);
|
|
285
|
+
for (const stats of sortedProposals) {
|
|
286
|
+
const contributors = Array.from(stats.contributors).join(";");
|
|
160
287
|
rows.push(
|
|
161
288
|
[
|
|
162
|
-
stats.
|
|
163
|
-
stats.statisticsPeriod || "-",
|
|
289
|
+
stats.proposal,
|
|
164
290
|
stats.commits,
|
|
165
|
-
|
|
166
|
-
`"${proposals}"`,
|
|
291
|
+
`"${contributors}"`,
|
|
167
292
|
stats.codeFilesChanged,
|
|
168
293
|
stats.additions,
|
|
169
294
|
stats.deletions,
|
|
170
|
-
stats.netChanges
|
|
171
|
-
((_a = stats.lastCommitDate) == null ? void 0 : _a.toISOString()) || ""
|
|
295
|
+
stats.netChanges
|
|
172
296
|
].join(",")
|
|
173
297
|
);
|
|
174
298
|
}
|
|
299
|
+
const totalProposals = result.proposals.size;
|
|
300
|
+
const totalProposalCommits = Array.from(result.proposals.values()).reduce((sum, p) => sum + p.commits, 0);
|
|
301
|
+
const totalProposalFiles = Array.from(result.proposals.values()).reduce((sum, p) => sum + p.codeFilesChanged, 0);
|
|
302
|
+
const totalProposalAdditions = Array.from(result.proposals.values()).reduce((sum, p) => sum + p.additions, 0);
|
|
303
|
+
const totalProposalDeletions = Array.from(result.proposals.values()).reduce((sum, p) => sum + p.deletions, 0);
|
|
304
|
+
const totalProposalNetChanges = Array.from(result.proposals.values()).reduce((sum, p) => sum + p.netChanges, 0);
|
|
305
|
+
rows.push("");
|
|
306
|
+
rows.push(`# ${(0, import_i18n.t)("output.proposalTotalLabel")}`);
|
|
307
|
+
rows.push(`${(0, import_i18n.t)("table.proposals")},${totalProposals}`);
|
|
308
|
+
rows.push(`${(0, import_i18n.t)("table.commits")},${totalProposalCommits}`);
|
|
309
|
+
rows.push(`${(0, import_i18n.t)("table.codeFiles")},${totalProposalFiles}`);
|
|
310
|
+
rows.push(`${(0, import_i18n.t)("table.additions")},${totalProposalAdditions}`);
|
|
311
|
+
rows.push(`${(0, import_i18n.t)("table.deletions")},${totalProposalDeletions}`);
|
|
312
|
+
rows.push(`${(0, import_i18n.t)("table.netChanges")},${totalProposalNetChanges}`);
|
|
313
|
+
rows.push(`
|
|
314
|
+
# ${(0, import_i18n.t)("output.authorSummary")}`);
|
|
315
|
+
const sortedAuthors = Array.from(result.authors.values()).sort((a, b) => b.commits - a.commits);
|
|
316
|
+
if (showContributors) {
|
|
317
|
+
rows.push(
|
|
318
|
+
`${(0, import_i18n.t)("table.author")},${(0, import_i18n.t)("table.period")},${(0, import_i18n.t)("table.commits")},${(0, import_i18n.t)("table.proposalsCount")},${(0, import_i18n.t)("table.proposalsList")},${(0, import_i18n.t)("table.codeFiles")},${(0, import_i18n.t)("table.additions")},${(0, import_i18n.t)("table.deletions")},${(0, import_i18n.t)("table.netChanges")},${(0, import_i18n.t)("table.lastCommitDate")}`
|
|
319
|
+
);
|
|
320
|
+
for (const stats of sortedAuthors) {
|
|
321
|
+
const proposals = Array.from(stats.openspecProposals).join(";");
|
|
322
|
+
rows.push(
|
|
323
|
+
[
|
|
324
|
+
stats.author,
|
|
325
|
+
stats.statisticsPeriod || "-",
|
|
326
|
+
stats.commits,
|
|
327
|
+
stats.openspecProposals.size,
|
|
328
|
+
`"${proposals}"`,
|
|
329
|
+
stats.codeFilesChanged,
|
|
330
|
+
stats.additions,
|
|
331
|
+
stats.deletions,
|
|
332
|
+
stats.netChanges,
|
|
333
|
+
((_a = stats.lastCommitDate) == null ? void 0 : _a.toISOString()) || ""
|
|
334
|
+
].join(",")
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
} else {
|
|
338
|
+
rows.push(
|
|
339
|
+
`${(0, import_i18n.t)("table.contributors")},${(0, import_i18n.t)("table.commits")},${(0, import_i18n.t)("table.proposals")},${(0, import_i18n.t)("table.codeFiles")},${(0, import_i18n.t)("table.additions")},${(0, import_i18n.t)("table.deletions")},${(0, import_i18n.t)("table.netChanges")}`
|
|
340
|
+
);
|
|
341
|
+
const totalProposalsSet = /* @__PURE__ */ new Set();
|
|
342
|
+
sortedAuthors.forEach((s) => s.openspecProposals.forEach((p) => totalProposalsSet.add(p)));
|
|
343
|
+
rows.push(
|
|
344
|
+
[
|
|
345
|
+
sortedAuthors.length,
|
|
346
|
+
sortedAuthors.reduce((sum, s) => sum + s.commits, 0),
|
|
347
|
+
totalProposalsSet.size,
|
|
348
|
+
sortedAuthors.reduce((sum, s) => sum + s.codeFilesChanged, 0),
|
|
349
|
+
sortedAuthors.reduce((sum, s) => sum + s.additions, 0),
|
|
350
|
+
sortedAuthors.reduce((sum, s) => sum + s.deletions, 0),
|
|
351
|
+
sortedAuthors.reduce((sum, s) => sum + s.netChanges, 0)
|
|
352
|
+
].join(",")
|
|
353
|
+
);
|
|
354
|
+
rows.push("");
|
|
355
|
+
rows.push(`# ${(0, import_i18n.t)("output.contributorHint")}`);
|
|
356
|
+
}
|
|
175
357
|
return rows.join("\n");
|
|
176
358
|
}
|
|
177
|
-
formatMarkdown(result) {
|
|
359
|
+
formatMarkdown(result, showContributors = true) {
|
|
178
360
|
let md = "";
|
|
179
361
|
md += (0, import_i18n.t)("markdown.title");
|
|
180
362
|
md += (0, import_i18n.t)("markdown.timeRange", {
|
|
@@ -183,27 +365,83 @@ ${stats.author}
|
|
|
183
365
|
});
|
|
184
366
|
md += (0, import_i18n.t)("markdown.branches", { branches: result.branches.join(", ") });
|
|
185
367
|
md += (0, import_i18n.t)("markdown.totalCommits", { count: String(result.totalCommits) });
|
|
186
|
-
md +=
|
|
187
|
-
|
|
368
|
+
md += `
|
|
369
|
+
## ${(0, import_i18n.t)("output.proposalSummary")}
|
|
370
|
+
|
|
188
371
|
`;
|
|
189
|
-
md += "
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
372
|
+
md += `| ${(0, import_i18n.t)("table.proposal")} | ${(0, import_i18n.t)("table.commits")} | ${(0, import_i18n.t)("table.contributors")} | ${(0, import_i18n.t)("table.codeFiles")} | ${(0, import_i18n.t)("table.additions")} | ${(0, import_i18n.t)("table.deletions")} | ${(0, import_i18n.t)("table.netChanges")} |
|
|
373
|
+
`;
|
|
374
|
+
md += "|--------|---------|-------------|------------|-----------|-----------|-------------|\n";
|
|
375
|
+
const sortedProposals = Array.from(result.proposals.values()).sort((a, b) => b.netChanges - a.netChanges);
|
|
376
|
+
for (const stats of sortedProposals) {
|
|
377
|
+
const contributors = Array.from(stats.contributors).join(", ");
|
|
378
|
+
md += `| ${stats.proposal} | ${stats.commits} | ${contributors} | ${stats.codeFilesChanged} | +${stats.additions} | -${stats.deletions} | ${stats.netChanges >= 0 ? "+" : ""}${stats.netChanges} |
|
|
193
379
|
`;
|
|
194
380
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
381
|
+
const totalProposals = result.proposals.size;
|
|
382
|
+
const totalProposalCommits = Array.from(result.proposals.values()).reduce((sum, p) => sum + p.commits, 0);
|
|
383
|
+
const totalProposalFiles = Array.from(result.proposals.values()).reduce((sum, p) => sum + p.codeFilesChanged, 0);
|
|
384
|
+
const totalProposalAdditions = Array.from(result.proposals.values()).reduce((sum, p) => sum + p.additions, 0);
|
|
385
|
+
const totalProposalDeletions = Array.from(result.proposals.values()).reduce((sum, p) => sum + p.deletions, 0);
|
|
386
|
+
const totalProposalNetChanges = Array.from(result.proposals.values()).reduce((sum, p) => sum + p.netChanges, 0);
|
|
387
|
+
md += `
|
|
388
|
+
**${(0, import_i18n.t)("output.proposalTotalLabel")}**
|
|
389
|
+
`;
|
|
390
|
+
md += `- ${(0, import_i18n.t)("table.proposals")}: ${totalProposals}
|
|
391
|
+
`;
|
|
392
|
+
md += `- ${(0, import_i18n.t)("table.commits")}: ${totalProposalCommits}
|
|
393
|
+
`;
|
|
394
|
+
md += `- ${(0, import_i18n.t)("table.codeFiles")}: ${totalProposalFiles}
|
|
395
|
+
`;
|
|
396
|
+
md += `- ${(0, import_i18n.t)("table.additions")}: +${totalProposalAdditions}
|
|
397
|
+
`;
|
|
398
|
+
md += `- ${(0, import_i18n.t)("table.deletions")}: -${totalProposalDeletions}
|
|
399
|
+
`;
|
|
400
|
+
md += `- ${(0, import_i18n.t)("table.netChanges")}: ${totalProposalNetChanges >= 0 ? "+" : ""}${totalProposalNetChanges}
|
|
401
|
+
`;
|
|
402
|
+
md += `
|
|
403
|
+
## ${(0, import_i18n.t)("output.authorSummary")}
|
|
199
404
|
|
|
200
405
|
`;
|
|
201
|
-
|
|
202
|
-
|
|
406
|
+
const sortedAuthors = Array.from(result.authors.values()).sort((a, b) => b.commits - a.commits);
|
|
407
|
+
if (showContributors) {
|
|
408
|
+
md += `| ${(0, import_i18n.t)("table.author")} | ${(0, import_i18n.t)("table.period")} | ${(0, import_i18n.t)("table.commits")} | ${(0, import_i18n.t)("table.proposals")} | ${(0, import_i18n.t)("table.codeFiles")} | ${(0, import_i18n.t)("table.additions")} | ${(0, import_i18n.t)("table.deletions")} | ${(0, import_i18n.t)("table.netChanges")} |
|
|
203
409
|
`;
|
|
410
|
+
md += "|--------|--------|---------|-----------|------------|-----------|-----------|-------------|\n";
|
|
411
|
+
for (const stats of sortedAuthors) {
|
|
412
|
+
md += `| ${stats.author} | ${stats.statisticsPeriod || "-"} | ${stats.commits} | ${stats.openspecProposals.size} | ${stats.codeFilesChanged} | +${stats.additions} | -${stats.deletions} | ${stats.netChanges >= 0 ? "+" : ""}${stats.netChanges} |
|
|
413
|
+
`;
|
|
414
|
+
}
|
|
415
|
+
md += (0, import_i18n.t)("markdown.proposalDetails");
|
|
416
|
+
for (const stats of sortedAuthors) {
|
|
417
|
+
if (stats.openspecProposals.size > 0) {
|
|
418
|
+
md += `### ${stats.author}
|
|
419
|
+
|
|
420
|
+
`;
|
|
421
|
+
for (const proposal of Array.from(stats.openspecProposals)) {
|
|
422
|
+
md += `- ${proposal}
|
|
423
|
+
`;
|
|
424
|
+
}
|
|
425
|
+
md += "\n";
|
|
204
426
|
}
|
|
205
|
-
md += "\n";
|
|
206
427
|
}
|
|
428
|
+
} else {
|
|
429
|
+
md += `| ${(0, import_i18n.t)("table.contributors")} | ${(0, import_i18n.t)("table.commits")} | ${(0, import_i18n.t)("table.proposals")} | ${(0, import_i18n.t)("table.codeFiles")} | ${(0, import_i18n.t)("table.additions")} | ${(0, import_i18n.t)("table.deletions")} | ${(0, import_i18n.t)("table.netChanges")} |
|
|
430
|
+
`;
|
|
431
|
+
md += "|------------|---------|-----------|------------|-----------|-----------|-------------|\n";
|
|
432
|
+
const totalProposalsSet = /* @__PURE__ */ new Set();
|
|
433
|
+
sortedAuthors.forEach((s) => s.openspecProposals.forEach((p) => totalProposalsSet.add(p)));
|
|
434
|
+
const totalAuthors = sortedAuthors.length;
|
|
435
|
+
const totalCommits = sortedAuthors.reduce((sum, s) => sum + s.commits, 0);
|
|
436
|
+
const totalFiles = sortedAuthors.reduce((sum, s) => sum + s.codeFilesChanged, 0);
|
|
437
|
+
const totalAdditions = sortedAuthors.reduce((sum, s) => sum + s.additions, 0);
|
|
438
|
+
const totalDeletions = sortedAuthors.reduce((sum, s) => sum + s.deletions, 0);
|
|
439
|
+
const totalNetChanges = sortedAuthors.reduce((sum, s) => sum + s.netChanges, 0);
|
|
440
|
+
md += `| ${totalAuthors} | ${totalCommits} | ${totalProposalsSet.size} | ${totalFiles} | +${totalAdditions} | -${totalDeletions} | ${totalNetChanges >= 0 ? "+" : ""}${totalNetChanges} |
|
|
441
|
+
`;
|
|
442
|
+
md += `
|
|
443
|
+
*${(0, import_i18n.t)("output.contributorHint")}*
|
|
444
|
+
`;
|
|
207
445
|
}
|
|
208
446
|
return md;
|
|
209
447
|
}
|
|
@@ -3,6 +3,7 @@ export declare class GitAnalyzer {
|
|
|
3
3
|
private git;
|
|
4
4
|
private config;
|
|
5
5
|
constructor(repoPath: string, config: Config);
|
|
6
|
+
fetchRemote(): Promise<void>;
|
|
6
7
|
getCommits(since: Date, until: Date, branches: string[]): Promise<CommitInfo[]>;
|
|
7
8
|
getCommitBranches(commitHash: string, targetBranches: string[]): Promise<string[]>;
|
|
8
9
|
analyzeCommit(commit: CommitInfo): Promise<CommitAnalysis | null>;
|
package/dist/cjs/git-analyzer.js
CHANGED
|
@@ -39,6 +39,12 @@ var GitAnalyzer = class {
|
|
|
39
39
|
this.git = (0, import_simple_git.default)(repoPath);
|
|
40
40
|
this.config = config;
|
|
41
41
|
}
|
|
42
|
+
async fetchRemote() {
|
|
43
|
+
try {
|
|
44
|
+
await this.git.fetch();
|
|
45
|
+
} catch (error) {
|
|
46
|
+
}
|
|
47
|
+
}
|
|
42
48
|
async getCommits(since, until, branches) {
|
|
43
49
|
const sinceStr = since.toISOString();
|
|
44
50
|
const untilStr = until.toISOString();
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"loading.config": "๐ Loading configuration...",
|
|
17
17
|
"loading.activeUsers": "๐ Fetching active users...",
|
|
18
18
|
"loading.analyzing": "๐ Analyzing commit history...",
|
|
19
|
+
"loading.fetching": "๐ Fetching remote branches...",
|
|
19
20
|
|
|
20
21
|
"info.timeRange": "๐
Time Range: {{since}} ~ {{until}}",
|
|
21
22
|
"info.branches": "๐ฟ Branches: {{branches}}",
|
|
@@ -48,11 +49,18 @@
|
|
|
48
49
|
"output.branches": "Branches: {{branches}}\n",
|
|
49
50
|
"output.totalCommits": "Total Commits: {{count}}\n\n",
|
|
50
51
|
"output.proposals": " Proposals: {{proposals}}\n",
|
|
52
|
+
"output.proposalSummary": "๐ Proposal Summary (by proposal)",
|
|
53
|
+
"output.proposalTotal": " ๐ Total: {{count}} proposals | {{commits}} commits | {{files}} files | +{{additions}}/-{{deletions}} lines (net: {{netChanges}})\n",
|
|
54
|
+
"output.proposalTotalLabel": "Proposal Summary Total",
|
|
55
|
+
"output.authorSummary": "๐ฅ Author Summary (by contributor)",
|
|
56
|
+
"output.contributorHint": "๐ก Use --show-contributors to see detailed statistics for each contributor",
|
|
51
57
|
|
|
52
58
|
"table.branch": "Branch",
|
|
53
59
|
"table.period": "Period",
|
|
54
60
|
"table.commits": "Commits",
|
|
55
61
|
"table.proposals": "Proposals",
|
|
62
|
+
"table.proposal": "Proposal",
|
|
63
|
+
"table.contributors": "Contributors",
|
|
56
64
|
"table.codeFiles": "Code Files",
|
|
57
65
|
"table.additions": "Additions",
|
|
58
66
|
"table.deletions": "Deletions",
|
|
@@ -68,5 +76,76 @@
|
|
|
68
76
|
"markdown.branches": "**Branches**: {{branches}}\n\n",
|
|
69
77
|
"markdown.totalCommits": "**Total Commits**: {{count}}\n\n",
|
|
70
78
|
"markdown.statistics": "## Statistics\n\n",
|
|
71
|
-
"markdown.proposalDetails": "\n## Proposal Details\n\n"
|
|
79
|
+
"markdown.proposalDetails": "\n## Proposal Details\n\n",
|
|
80
|
+
|
|
81
|
+
"multi.beta.warning": "โ ๏ธ BETA: Multi-repository mode is experimental",
|
|
82
|
+
"multi.beta.feedback": " Please report issues at: https://github.com/Orchardxyz/openspec-stat/issues",
|
|
83
|
+
"multi.loading.config": "๐ Loading multi-repository configuration...",
|
|
84
|
+
"multi.repo.cloning": "โ๏ธ Cloning {{repo}}...",
|
|
85
|
+
"multi.repo.cloned": "โ
Successfully cloned {{repo}}",
|
|
86
|
+
"multi.repo.fetching": "๐ Fetching remote branches for {{repo}}...",
|
|
87
|
+
"multi.repo.analyzing": "๐ Analyzing {{repo}} ({{type}})...",
|
|
88
|
+
"multi.repo.completed": "โ
Completed {{repo}}: {{commits}} commits",
|
|
89
|
+
"multi.repo.failed": "โ Failed {{repo}}: {{error}}",
|
|
90
|
+
"multi.repo.skipped": "โญ๏ธ Skipped {{repo}}: disabled",
|
|
91
|
+
"multi.cleanup.start": "๐งน Cleaning up temporary directories...",
|
|
92
|
+
"multi.cleanup.done": "โ
Cleanup completed",
|
|
93
|
+
"multi.summary.title": "\n๐ฆ Multi-Repository Summary\n",
|
|
94
|
+
"multi.summary.repos": "Repositories: {{total}} ({{success}} succeeded, {{failed}} failed)",
|
|
95
|
+
"multi.progress.batch": "Processing batch {{current}}/{{total}}...",
|
|
96
|
+
"multi.table.repository": "Repository",
|
|
97
|
+
"multi.table.type": "Type",
|
|
98
|
+
|
|
99
|
+
"init.welcome": "\n๐ OpenSpec Configuration Wizard\n",
|
|
100
|
+
"init.welcomeMulti": "\n๐ OpenSpec Multi-Repository Configuration Wizard (BETA)\n",
|
|
101
|
+
"init.configName": "Configuration file name:",
|
|
102
|
+
"init.addRepository": "\n๐ฆ Repository {{number}}",
|
|
103
|
+
"init.repoType": "Repository type:",
|
|
104
|
+
"init.repoType.local": "๐ Local - I have this repository on my machine",
|
|
105
|
+
"init.repoType.remote": "โ๏ธ Remote - Clone from remote URL",
|
|
106
|
+
"init.repoName": "Repository name (for display):",
|
|
107
|
+
"init.repoPath": "Local path (absolute or relative):",
|
|
108
|
+
"init.repoUrl": "Git URL (e.g., git@github.com:org/repo.git):",
|
|
109
|
+
"init.repoUrlInvalid": "Invalid Git URL format",
|
|
110
|
+
"init.useFullClone": "Use full clone (recommended for accuracy)?",
|
|
111
|
+
"init.cloneDepth": "Clone depth (number of commits):",
|
|
112
|
+
"init.branches": "Branches to analyze (comma-separated):",
|
|
113
|
+
"init.addMore": "Add another repository?",
|
|
114
|
+
"init.timeConfig": "\nโฐ Time Range Configuration",
|
|
115
|
+
"init.useDefaultTime": "Use default time range (yesterday 20:00 - today 20:00)?",
|
|
116
|
+
"init.sinceHours": "Start time offset (hours, negative for past):",
|
|
117
|
+
"init.untilHours": "End time (hour of day, 0-23):",
|
|
118
|
+
"init.advanced": "\nโ๏ธ Advanced Options",
|
|
119
|
+
"init.configureAdvanced": "Configure advanced options?",
|
|
120
|
+
"init.openspecDir": "OpenSpec directory path:",
|
|
121
|
+
"init.maxConcurrent": "Maximum concurrent repository operations:",
|
|
122
|
+
"init.authorMapping": "Configure author name mapping?",
|
|
123
|
+
"init.authorMappingInfo": "\nAuthor mapping helps unify multiple Git identities for the same person.\n",
|
|
124
|
+
"init.gitIdentity": "Git identity (name or email):",
|
|
125
|
+
"init.unifiedName": "Unified name for \"{{identity}}\":",
|
|
126
|
+
"init.addMoreMapping": "Add another mapping?",
|
|
127
|
+
"init.preview": "\nโ
Configuration Preview:\n",
|
|
128
|
+
"init.save": "\nSave this configuration?",
|
|
129
|
+
"init.saved": "\nโ
Configuration saved to {{path}}",
|
|
130
|
+
"init.runCommand": "\nRun: openspec-stat multi -c {{path}}",
|
|
131
|
+
"init.templateCreated": "โ
Template created at {{path}}",
|
|
132
|
+
"init.templateEdit": "Please edit the file and configure your repositories.",
|
|
133
|
+
|
|
134
|
+
"config.validation.noRepos": "Invalid config: \"repositories\" must be an array",
|
|
135
|
+
"config.validation.emptyRepos": "Invalid config: at least one repository is required",
|
|
136
|
+
"config.validation.noName": "Repository {{index}}: \"name\" is required",
|
|
137
|
+
"config.validation.invalidType": "Repository \"{{name}}\": \"type\" must be \"local\" or \"remote\"",
|
|
138
|
+
"config.validation.noPath": "Repository \"{{name}}\": \"path\" is required for local type",
|
|
139
|
+
"config.validation.noUrl": "Repository \"{{name}}\": \"url\" is required for remote type",
|
|
140
|
+
"config.validation.noBranches": "Repository \"{{name}}\": at least one branch is required",
|
|
141
|
+
"config.summary.title": "\n๐ Configuration Summary\n",
|
|
142
|
+
"config.summary.repositories": "Repositories:",
|
|
143
|
+
"config.summary.timeRange": "\nTime Range:",
|
|
144
|
+
"config.summary.since": " Since: {{hours}} hours",
|
|
145
|
+
"config.summary.until": " Until: {{hours}}:00",
|
|
146
|
+
"config.summary.parallelism": "\nParallelism:",
|
|
147
|
+
"config.summary.maxConcurrent": " Max concurrent: {{count}}",
|
|
148
|
+
"config.summary.remoteCache": "\nRemote Cache:",
|
|
149
|
+
"config.summary.cacheDir": " Directory: {{dir}}",
|
|
150
|
+
"config.summary.autoCleanup": " Auto cleanup: {{enabled}}"
|
|
72
151
|
}
|