locmeter 0.1.1 → 0.1.2
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 +1 -0
- package/bin/locmeter.js +70 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,6 +44,7 @@ Common options:
|
|
|
44
44
|
- default `--to`: today
|
|
45
45
|
- default `--from`: one year before `--to`
|
|
46
46
|
- default author identity: auto-detected from your current `gh` login
|
|
47
|
+
- default repo root: current directory, then common roots like `~/Developer`, `~/Code`, `~/Projects`
|
|
47
48
|
- `--from YYYY-MM-DD`
|
|
48
49
|
- `--to YYYY-MM-DD`
|
|
49
50
|
- `--days N`
|
package/bin/locmeter.js
CHANGED
|
@@ -101,10 +101,13 @@ const MONTHS = {
|
|
|
101
101
|
12: "DEC"
|
|
102
102
|
};
|
|
103
103
|
|
|
104
|
+
function logStep(message) {
|
|
105
|
+
process.stderr.write(`${message}\n`);
|
|
106
|
+
}
|
|
107
|
+
|
|
104
108
|
function parseArgs(argv) {
|
|
105
109
|
const args = {
|
|
106
110
|
bucket: "week",
|
|
107
|
-
root: process.cwd(),
|
|
108
111
|
authorEmail: [],
|
|
109
112
|
authorName: [],
|
|
110
113
|
output: "github-lines-changed.png",
|
|
@@ -227,6 +230,14 @@ async function runText(command, args, cwd) {
|
|
|
227
230
|
return stdout;
|
|
228
231
|
}
|
|
229
232
|
|
|
233
|
+
async function tryRunText(command, args, cwd) {
|
|
234
|
+
try {
|
|
235
|
+
return await runText(command, args, cwd);
|
|
236
|
+
} catch (error) {
|
|
237
|
+
return "";
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
230
241
|
async function getLogin() {
|
|
231
242
|
const user = await runJson("gh", ["api", "user"]);
|
|
232
243
|
return user.login;
|
|
@@ -266,6 +277,32 @@ function resolveRepoPaths(repoNames, root) {
|
|
|
266
277
|
return { resolved, missing };
|
|
267
278
|
}
|
|
268
279
|
|
|
280
|
+
function candidateRoots(explicitRoot) {
|
|
281
|
+
if (explicitRoot) return [path.resolve(explicitRoot)];
|
|
282
|
+
const home = os.homedir();
|
|
283
|
+
const values = [
|
|
284
|
+
process.cwd(),
|
|
285
|
+
path.join(home, "Developer"),
|
|
286
|
+
path.join(home, "Code"),
|
|
287
|
+
path.join(home, "Projects"),
|
|
288
|
+
home
|
|
289
|
+
];
|
|
290
|
+
return [...new Set(values.map((value) => path.resolve(value)))];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function resolveRepoPathsFromCandidates(repoNames, explicitRoot) {
|
|
294
|
+
const candidates = candidateRoots(explicitRoot);
|
|
295
|
+
let best = { resolved: [], missing: repoNames, root: candidates[0] };
|
|
296
|
+
for (const root of candidates) {
|
|
297
|
+
const result = resolveRepoPaths(repoNames, root);
|
|
298
|
+
if (result.resolved.length > best.resolved.length) {
|
|
299
|
+
best = { ...result, root };
|
|
300
|
+
}
|
|
301
|
+
if (result.resolved.length === repoNames.length) return { ...result, root };
|
|
302
|
+
}
|
|
303
|
+
return best;
|
|
304
|
+
}
|
|
305
|
+
|
|
269
306
|
async function autodetectAuthorIdentities(repoPaths, login) {
|
|
270
307
|
const pairs = new Map();
|
|
271
308
|
const loginLower = login.toLowerCase();
|
|
@@ -297,6 +334,15 @@ async function autodetectAuthorIdentities(repoPaths, login) {
|
|
|
297
334
|
return { emails, names };
|
|
298
335
|
}
|
|
299
336
|
|
|
337
|
+
async function gitConfigIdentity() {
|
|
338
|
+
const email = (await tryRunText("git", ["config", "--global", "user.email"])).trim();
|
|
339
|
+
const name = (await tryRunText("git", ["config", "--global", "user.name"])).trim();
|
|
340
|
+
return {
|
|
341
|
+
emails: email ? [email] : [],
|
|
342
|
+
names: name ? [name] : []
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
300
346
|
function escapeRegex(value) {
|
|
301
347
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
302
348
|
}
|
|
@@ -650,27 +696,43 @@ function renderChart(series, login, startDate, endDate, bucket, outputPath) {
|
|
|
650
696
|
async function main() {
|
|
651
697
|
const args = parseArgs(process.argv.slice(2));
|
|
652
698
|
const { startDate, endDate } = computeDates(args);
|
|
653
|
-
const root = path.resolve(args.root);
|
|
654
699
|
const output = path.resolve(args.output);
|
|
655
700
|
const jsonOutput = path.resolve(args.jsonOutput);
|
|
656
701
|
|
|
702
|
+
logStep("Resolving GitHub identity...");
|
|
657
703
|
const login = await getLogin();
|
|
704
|
+
logStep(`Fetching contributed repositories for ${login}...`);
|
|
658
705
|
const repoNames = await getRepositories(login);
|
|
659
|
-
const { resolved: repoPaths, missing } =
|
|
706
|
+
const { resolved: repoPaths, missing, root } = resolveRepoPathsFromCandidates(repoNames, args.root);
|
|
707
|
+
|
|
708
|
+
if (!repoPaths.length) {
|
|
709
|
+
throw new Error(
|
|
710
|
+
`could not find any locally cloned contributed repos under ${root}; pass --root to the directory that contains your repo clones`
|
|
711
|
+
);
|
|
712
|
+
}
|
|
660
713
|
|
|
661
714
|
let authorEmails = [...new Set(args.authorEmail)];
|
|
662
715
|
let authorNames = [...new Set(args.authorName)];
|
|
663
716
|
|
|
664
717
|
if (!authorEmails.length && !authorNames.length) {
|
|
718
|
+
logStep(`Detecting author identity from ${repoPaths.length} local repos under ${root}...`);
|
|
665
719
|
const detected = await autodetectAuthorIdentities(repoPaths, login);
|
|
666
720
|
authorEmails = detected.emails;
|
|
667
721
|
authorNames = detected.names;
|
|
668
722
|
}
|
|
669
723
|
|
|
724
|
+
if (!authorEmails.length && !authorNames.length) {
|
|
725
|
+
logStep("Falling back to global git identity...");
|
|
726
|
+
const detected = await gitConfigIdentity();
|
|
727
|
+
authorEmails = detected.emails;
|
|
728
|
+
authorNames = [...new Set([login, ...detected.names])];
|
|
729
|
+
}
|
|
730
|
+
|
|
670
731
|
if (!authorEmails.length && !authorNames.length) {
|
|
671
732
|
throw new Error("could not auto-detect your author identity; pass --author-email or --author-name");
|
|
672
733
|
}
|
|
673
734
|
|
|
735
|
+
logStep(`Fetching commits from ${repoPaths.length} repos...`);
|
|
674
736
|
const { series, rawDaily } = await aggregate(
|
|
675
737
|
repoPaths,
|
|
676
738
|
startDate,
|
|
@@ -680,6 +742,7 @@ async function main() {
|
|
|
680
742
|
authorNames
|
|
681
743
|
);
|
|
682
744
|
|
|
745
|
+
logStep(`Crunching numbers for ${args.bucket} buckets from ${dateIso(startDate)} to ${dateIso(endDate)}...`);
|
|
683
746
|
renderChart(series, login, startDate, endDate, args.bucket, output);
|
|
684
747
|
|
|
685
748
|
const values = Object.values(series);
|
|
@@ -693,6 +756,7 @@ async function main() {
|
|
|
693
756
|
bucket: args.bucket,
|
|
694
757
|
author_emails: authorEmails,
|
|
695
758
|
author_names: authorNames,
|
|
759
|
+
root_used: root,
|
|
696
760
|
local_repositories_used: repoPaths.map(([name]) => name),
|
|
697
761
|
missing_repositories: missing,
|
|
698
762
|
total_lines_changed: values.reduce((sum, value) => sum + value, 0),
|
|
@@ -705,6 +769,9 @@ async function main() {
|
|
|
705
769
|
)
|
|
706
770
|
);
|
|
707
771
|
|
|
772
|
+
logStep("Created output files:");
|
|
773
|
+
logStep(` PNG: ${output}`);
|
|
774
|
+
logStep(` JSON: ${jsonOutput}`);
|
|
708
775
|
process.stdout.write(`${output}\n${jsonOutput}\n`);
|
|
709
776
|
}
|
|
710
777
|
|