@simtlix/simfinity-js 2.4.4 → 2.4.6
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/git-report.js +224 -0
- package/package.json +1 -1
- package/src/index.js +2 -2
package/git-report.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { writeFileSync } from 'node:fs';
|
|
5
|
+
import { resolve } from 'node:path';
|
|
6
|
+
|
|
7
|
+
const CHANGE_TYPE_LABELS = {
|
|
8
|
+
A: 'Added',
|
|
9
|
+
M: 'Modified',
|
|
10
|
+
D: 'Deleted',
|
|
11
|
+
R: 'Renamed',
|
|
12
|
+
C: 'Copied',
|
|
13
|
+
T: 'TypeChanged',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const COMMIT_DELIMITER = '---GIT_REPORT_COMMIT---';
|
|
17
|
+
const FIELD_DELIMITER = '---GIT_REPORT_FIELD---';
|
|
18
|
+
|
|
19
|
+
function printHelp() {
|
|
20
|
+
const help = `
|
|
21
|
+
Usage: node git-report.js --branch <branch> --author <author> --months <N> [--output <file.csv>]
|
|
22
|
+
|
|
23
|
+
Generate a CSV report of git commits for a given author on a branch within a time period.
|
|
24
|
+
|
|
25
|
+
Required arguments:
|
|
26
|
+
--branch <branch> Git branch name to analyze
|
|
27
|
+
--author <author> Git author name or email to filter by
|
|
28
|
+
--months <N> Number of months to look back from today
|
|
29
|
+
|
|
30
|
+
Optional arguments:
|
|
31
|
+
--output <file> Output CSV file path (default: git-report.csv)
|
|
32
|
+
--help, -h Show this help message and exit
|
|
33
|
+
|
|
34
|
+
Output:
|
|
35
|
+
A CSV file with one row per affected file per commit, containing:
|
|
36
|
+
commit_hash, author, date, message, change_type, file_path
|
|
37
|
+
|
|
38
|
+
Examples:
|
|
39
|
+
node git-report.js --branch master --author "John Doe" --months 3
|
|
40
|
+
node git-report.js --branch main --author john@example.com --months 6 --output report.csv
|
|
41
|
+
`;
|
|
42
|
+
console.log(help.trim());
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseArgs(argv) {
|
|
46
|
+
const args = argv.slice(2);
|
|
47
|
+
|
|
48
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
49
|
+
printHelp();
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const opts = {};
|
|
54
|
+
for (let i = 0; i < args.length; i++) {
|
|
55
|
+
switch (args[i]) {
|
|
56
|
+
case '--branch':
|
|
57
|
+
opts.branch = args[++i];
|
|
58
|
+
break;
|
|
59
|
+
case '--author':
|
|
60
|
+
opts.author = args[++i];
|
|
61
|
+
break;
|
|
62
|
+
case '--months':
|
|
63
|
+
opts.months = args[++i];
|
|
64
|
+
break;
|
|
65
|
+
case '--output':
|
|
66
|
+
opts.output = args[++i];
|
|
67
|
+
break;
|
|
68
|
+
default:
|
|
69
|
+
console.error(`Unknown argument: ${args[i]}`);
|
|
70
|
+
printHelp();
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const missing = [];
|
|
76
|
+
if (!opts.branch) missing.push('--branch');
|
|
77
|
+
if (!opts.author) missing.push('--author');
|
|
78
|
+
if (!opts.months) missing.push('--months');
|
|
79
|
+
|
|
80
|
+
if (missing.length > 0) {
|
|
81
|
+
console.error(`Missing required arguments: ${missing.join(', ')}`);
|
|
82
|
+
printHelp();
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const months = parseInt(opts.months, 10);
|
|
87
|
+
if (Number.isNaN(months) || months <= 0) {
|
|
88
|
+
console.error('--months must be a positive integer');
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
opts.months = months;
|
|
92
|
+
opts.output = opts.output || 'git-report.csv';
|
|
93
|
+
|
|
94
|
+
return opts;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function validateGitEnvironment() {
|
|
98
|
+
try {
|
|
99
|
+
execFileSync('git', ['--version'], { stdio: 'pipe' });
|
|
100
|
+
} catch {
|
|
101
|
+
console.error('Error: git is not installed or not found in PATH.');
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
execFileSync('git', ['rev-parse', '--is-inside-work-tree'], { stdio: 'pipe' });
|
|
107
|
+
} catch {
|
|
108
|
+
console.error('Error: current directory is not inside a git repository.');
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function computeSinceDate(months) {
|
|
114
|
+
const date = new Date();
|
|
115
|
+
date.setMonth(date.getMonth() - months);
|
|
116
|
+
return date.toISOString().slice(0, 10);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function escapeCsvField(value) {
|
|
120
|
+
const str = String(value).replace(/\r?\n/g, ' ');
|
|
121
|
+
if (str.includes(',') || str.includes('"') || str.includes('\n')) {
|
|
122
|
+
return `"${str.replace(/"/g, '""')}"`;
|
|
123
|
+
}
|
|
124
|
+
return str;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getGitLog(branch, author, sinceDate) {
|
|
128
|
+
const format = [
|
|
129
|
+
`${COMMIT_DELIMITER}`,
|
|
130
|
+
`%h${FIELD_DELIMITER}%an${FIELD_DELIMITER}%ai${FIELD_DELIMITER}%s`,
|
|
131
|
+
].join('');
|
|
132
|
+
|
|
133
|
+
const args = [
|
|
134
|
+
'log',
|
|
135
|
+
`--format=${format}`,
|
|
136
|
+
'--name-status',
|
|
137
|
+
`--author=${author}`,
|
|
138
|
+
`--since=${sinceDate}`,
|
|
139
|
+
branch,
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
return execFileSync('git', args, { encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 });
|
|
144
|
+
} catch (err) {
|
|
145
|
+
console.error(`Error running git log: ${err.message}`);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function parseGitLog(rawOutput) {
|
|
151
|
+
const rows = [];
|
|
152
|
+
const commits = rawOutput.split(COMMIT_DELIMITER).filter((s) => s.trim());
|
|
153
|
+
|
|
154
|
+
for (const block of commits) {
|
|
155
|
+
const lines = block.trim().split('\n');
|
|
156
|
+
if (lines.length === 0) continue;
|
|
157
|
+
|
|
158
|
+
const headerLine = lines[0];
|
|
159
|
+
const parts = headerLine.split(FIELD_DELIMITER);
|
|
160
|
+
if (parts.length < 4) continue;
|
|
161
|
+
|
|
162
|
+
const [hash, author, dateRaw, message] = parts;
|
|
163
|
+
const date = dateRaw.slice(0, 10);
|
|
164
|
+
|
|
165
|
+
for (let i = 1; i < lines.length; i++) {
|
|
166
|
+
const line = lines[i].trim();
|
|
167
|
+
if (!line) continue;
|
|
168
|
+
|
|
169
|
+
const match = line.match(/^([AMDRTC])\d*\t(.+)$/);
|
|
170
|
+
if (!match) continue;
|
|
171
|
+
|
|
172
|
+
const statusCode = match[1];
|
|
173
|
+
const filePath = match[2].includes('\t') ? match[2].split('\t').pop() : match[2];
|
|
174
|
+
const changeType = CHANGE_TYPE_LABELS[statusCode] || statusCode;
|
|
175
|
+
|
|
176
|
+
rows.push({
|
|
177
|
+
commit_hash: hash,
|
|
178
|
+
author,
|
|
179
|
+
date,
|
|
180
|
+
message,
|
|
181
|
+
change_type: changeType,
|
|
182
|
+
file_path: filePath,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return rows;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function writeCsv(rows, outputPath) {
|
|
191
|
+
const columns = ['commit_hash', 'author', 'date', 'message', 'change_type', 'file_path'];
|
|
192
|
+
const header = columns.join(',');
|
|
193
|
+
const lines = rows.map((row) => columns.map((col) => escapeCsvField(row[col])).join(','));
|
|
194
|
+
|
|
195
|
+
const csv = [header, ...lines, ''].join('\n');
|
|
196
|
+
const absPath = resolve(outputPath);
|
|
197
|
+
writeFileSync(absPath, csv, 'utf-8');
|
|
198
|
+
return absPath;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function main() {
|
|
202
|
+
const opts = parseArgs(process.argv);
|
|
203
|
+
validateGitEnvironment();
|
|
204
|
+
|
|
205
|
+
const sinceDate = computeSinceDate(opts.months);
|
|
206
|
+
console.error(`Searching commits on branch "${opts.branch}" by "${opts.author}" since ${sinceDate}...`);
|
|
207
|
+
|
|
208
|
+
const rawLog = getGitLog(opts.branch, opts.author, sinceDate);
|
|
209
|
+
const rows = parseGitLog(rawLog);
|
|
210
|
+
|
|
211
|
+
const uniqueCommits = new Set(rows.map((r) => r.commit_hash)).size;
|
|
212
|
+
|
|
213
|
+
if (rows.length === 0) {
|
|
214
|
+
console.error('No commits found matching the criteria.');
|
|
215
|
+
process.exit(0);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const absPath = writeCsv(rows, opts.output);
|
|
219
|
+
|
|
220
|
+
console.error(`Done. ${uniqueCommits} commit(s), ${rows.length} file change(s).`);
|
|
221
|
+
console.error(`Report written to: ${absPath}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
main();
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -416,8 +416,8 @@ const buildInputType = (gqltype) => {
|
|
|
416
416
|
if (fieldEntry.type.ofType === gqltype) {
|
|
417
417
|
selfReferenceCollections[fieldEntryName] = fieldEntry;
|
|
418
418
|
} else {
|
|
419
|
-
const listInputTypeForAdd = graphQLListInputType(typesDict, fieldEntry, fieldEntryName, 'A', fieldEntry.extensions?.relation?.connectionField);
|
|
420
|
-
const listInputTypeForUpdate = graphQLListInputType(typesDictForUpdate, fieldEntry, fieldEntryName, 'U', fieldEntry.extensions?.relation?.connectionField);
|
|
419
|
+
const listInputTypeForAdd = graphQLListInputType(typesDict, fieldEntry, fieldEntryName, gqltype.name + 'A', fieldEntry.extensions?.relation?.connectionField);
|
|
420
|
+
const listInputTypeForUpdate = graphQLListInputType(typesDictForUpdate, fieldEntry, fieldEntryName, gqltype.name +'U', fieldEntry.extensions?.relation?.connectionField);
|
|
421
421
|
if (listInputTypeForAdd && listInputTypeForUpdate) {
|
|
422
422
|
fieldArg.type = listInputTypeForAdd;
|
|
423
423
|
fieldArgForUpdate.type = listInputTypeForUpdate;
|