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 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 } = resolveRepoPaths(repoNames, root);
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "locmeter",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Render a PNG chart of lines changed over time from your GitHub contribution repos.",
5
5
  "license": "MIT",
6
6
  "preferGlobal": true,