locmeter 0.1.3 → 0.2.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 +16 -21
- package/bin/locmeter.js +158 -42
- package/examples/jojo-weekly-added-deleted-sum.png +0 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -12,6 +12,8 @@ npx locmeter
|
|
|
12
12
|
|
|
13
13
|

|
|
14
14
|
|
|
15
|
+

|
|
16
|
+
|
|
15
17
|
## Requirements
|
|
16
18
|
|
|
17
19
|
- Node.js 18+
|
|
@@ -41,6 +43,7 @@ npm install -g locmeter
|
|
|
41
43
|
Common options:
|
|
42
44
|
|
|
43
45
|
- default bucket: `week`
|
|
46
|
+
- default mode: `sum`
|
|
44
47
|
- default `--to`: today
|
|
45
48
|
- default `--from`: one year before `--to`
|
|
46
49
|
- default author identity: auto-detected from your current `gh` login
|
|
@@ -50,6 +53,7 @@ Common options:
|
|
|
50
53
|
- `--to YYYY-MM-DD`
|
|
51
54
|
- `--days N`
|
|
52
55
|
- `--bucket day|week|month`
|
|
56
|
+
- `--mode sum|added|deleted|added/deleted|added/deleted/sum`
|
|
53
57
|
- `--root /path/to/repos`
|
|
54
58
|
- `--search-depth N`
|
|
55
59
|
- `--author-email you@example.com`
|
|
@@ -57,37 +61,28 @@ Common options:
|
|
|
57
61
|
- `--output chart.png`
|
|
58
62
|
- `--json-output data.json`
|
|
59
63
|
|
|
60
|
-
|
|
64
|
+
Modes:
|
|
61
65
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
```
|
|
66
|
+
- `sum`
|
|
67
|
+
- `added`
|
|
68
|
+
- `deleted`
|
|
69
|
+
- `added/deleted`
|
|
70
|
+
- `added/deleted/sum`
|
|
68
71
|
|
|
69
|
-
|
|
72
|
+
Example:
|
|
70
73
|
|
|
71
74
|
```bash
|
|
72
75
|
locmeter \
|
|
73
76
|
--root ~/Developer \
|
|
74
|
-
--
|
|
75
|
-
--
|
|
77
|
+
--bucket week \
|
|
78
|
+
--mode added/deleted/sum \
|
|
79
|
+
--output examples/jojo-weekly-added-deleted-sum.png \
|
|
80
|
+
--json-output examples/jojo-weekly-added-deleted-sum.json
|
|
76
81
|
```
|
|
77
82
|
|
|
78
|
-
That example produced:
|
|
79
|
-
|
|
80
|
-
- `examples/jojo-weekly.png`
|
|
81
|
-
- `examples/jojo-weekly.json`
|
|
82
|
-
- date range: `2025-03-13` to `2026-03-13`
|
|
83
|
-
- bucket: `week`
|
|
84
|
-
- total lines changed: `1,059,347`
|
|
85
|
-
- peak week: `207,431`
|
|
86
|
-
|
|
87
83
|
The CLI prints the generated PNG path and JSON path on success.
|
|
88
84
|
|
|
89
85
|
## Notes
|
|
90
86
|
|
|
91
87
|
- `locmeter` is intended for global CLI usage.
|
|
92
|
-
-
|
|
93
|
-
- The published package only ships the example PNG, not the example JSON.
|
|
88
|
+
- JSON output includes separate `added`, `deleted`, and `sum` series.
|
package/bin/locmeter.js
CHANGED
|
@@ -13,9 +13,31 @@ const BG = [247, 248, 250];
|
|
|
13
13
|
const PLOT_BG = [255, 255, 255];
|
|
14
14
|
const GRID = [226, 232, 240];
|
|
15
15
|
const AXIS = [100, 116, 139];
|
|
16
|
-
const
|
|
17
|
-
|
|
16
|
+
const SERIES_STYLES = {
|
|
17
|
+
sum: {
|
|
18
|
+
line: [15, 23, 42],
|
|
19
|
+
fill: [191, 219, 254],
|
|
20
|
+
legend: "SUM"
|
|
21
|
+
},
|
|
22
|
+
added: {
|
|
23
|
+
line: [22, 163, 74],
|
|
24
|
+
fill: [187, 247, 208],
|
|
25
|
+
legend: "ADDED"
|
|
26
|
+
},
|
|
27
|
+
deleted: {
|
|
28
|
+
line: [220, 38, 38],
|
|
29
|
+
fill: [254, 202, 202],
|
|
30
|
+
legend: "DELETED"
|
|
31
|
+
}
|
|
32
|
+
};
|
|
18
33
|
const TEXT = [30, 41, 59];
|
|
34
|
+
const MODES = {
|
|
35
|
+
sum: ["sum"],
|
|
36
|
+
added: ["added"],
|
|
37
|
+
deleted: ["deleted"],
|
|
38
|
+
"added/deleted": ["added", "deleted"],
|
|
39
|
+
"added/deleted/sum": ["added", "deleted", "sum"]
|
|
40
|
+
};
|
|
19
41
|
|
|
20
42
|
const FONT = {
|
|
21
43
|
"0": ["01110", "10001", "10011", "10101", "11001", "10001", "01110"],
|
|
@@ -108,6 +130,7 @@ function logStep(message) {
|
|
|
108
130
|
function parseArgs(argv) {
|
|
109
131
|
const args = {
|
|
110
132
|
bucket: "week",
|
|
133
|
+
mode: "sum",
|
|
111
134
|
searchDepth: 3,
|
|
112
135
|
authorEmail: [],
|
|
113
136
|
authorName: [],
|
|
@@ -129,6 +152,7 @@ function parseArgs(argv) {
|
|
|
129
152
|
else if (arg === "--from") args.fromDate = next();
|
|
130
153
|
else if (arg === "--to") args.toDate = next();
|
|
131
154
|
else if (arg === "--bucket") args.bucket = next();
|
|
155
|
+
else if (arg === "--mode") args.mode = next();
|
|
132
156
|
else if (arg === "--root") args.root = next();
|
|
133
157
|
else if (arg === "--search-depth") args.searchDepth = Number(next());
|
|
134
158
|
else if (arg === "--author-email") args.authorEmail.push(next());
|
|
@@ -146,6 +170,9 @@ function parseArgs(argv) {
|
|
|
146
170
|
if (!["day", "week", "month"].includes(args.bucket)) {
|
|
147
171
|
throw new Error("--bucket must be day, week, or month");
|
|
148
172
|
}
|
|
173
|
+
if (!Object.hasOwn(MODES, args.mode)) {
|
|
174
|
+
throw new Error(`--mode must be one of ${Object.keys(MODES).join(", ")}`);
|
|
175
|
+
}
|
|
149
176
|
if (args.days !== undefined && (!Number.isInteger(args.days) || args.days <= 0)) {
|
|
150
177
|
throw new Error("--days must be a positive integer");
|
|
151
178
|
}
|
|
@@ -163,6 +190,7 @@ function printHelp() {
|
|
|
163
190
|
"",
|
|
164
191
|
"Defaults:",
|
|
165
192
|
" --bucket week",
|
|
193
|
+
" --mode sum",
|
|
166
194
|
" --to today",
|
|
167
195
|
" --from one year before --to",
|
|
168
196
|
" --author-email/--author-name auto-detected from current gh login",
|
|
@@ -173,6 +201,7 @@ function printHelp() {
|
|
|
173
201
|
" --from YYYY-MM-DD",
|
|
174
202
|
" --to YYYY-MM-DD",
|
|
175
203
|
" --bucket day|week|month",
|
|
204
|
+
" --mode sum|added|deleted|added/deleted|added/deleted/sum",
|
|
176
205
|
" --root /path/to/repos",
|
|
177
206
|
" --search-depth N",
|
|
178
207
|
" --author-email you@example.com",
|
|
@@ -497,16 +526,30 @@ async function aggregate(repoPaths, startDate, endDate, bucket, authorEmails, au
|
|
|
497
526
|
const rawDaily = new Map();
|
|
498
527
|
const seen = new Set();
|
|
499
528
|
|
|
529
|
+
function ensureTotals(store, key) {
|
|
530
|
+
if (!store.has(key)) {
|
|
531
|
+
store.set(key, { added: 0, deleted: 0, sum: 0 });
|
|
532
|
+
}
|
|
533
|
+
return store.get(key);
|
|
534
|
+
}
|
|
535
|
+
|
|
500
536
|
for (const output of outputs) {
|
|
501
537
|
let current = null;
|
|
502
|
-
let
|
|
538
|
+
let runningTotals = { added: 0, deleted: 0, sum: 0 };
|
|
503
539
|
|
|
504
540
|
for (const line of output.split("\n")) {
|
|
505
541
|
if (line.startsWith("COMMIT\t")) {
|
|
506
542
|
if (current && !seen.has(current.sha)) {
|
|
507
543
|
seen.add(current.sha);
|
|
508
|
-
rawDaily
|
|
509
|
-
|
|
544
|
+
const daily = ensureTotals(rawDaily, current.date);
|
|
545
|
+
daily.added += runningTotals.added;
|
|
546
|
+
daily.deleted += runningTotals.deleted;
|
|
547
|
+
daily.sum += runningTotals.sum;
|
|
548
|
+
|
|
549
|
+
const bucketTotal = ensureTotals(bucketTotals, current.bucket);
|
|
550
|
+
bucketTotal.added += runningTotals.added;
|
|
551
|
+
bucketTotal.deleted += runningTotals.deleted;
|
|
552
|
+
bucketTotal.sum += runningTotals.sum;
|
|
510
553
|
}
|
|
511
554
|
const [, sha, dateStr] = line.split("\t", 5);
|
|
512
555
|
const commitDate = parseDate(dateStr);
|
|
@@ -515,7 +558,7 @@ async function aggregate(repoPaths, startDate, endDate, bucket, authorEmails, au
|
|
|
515
558
|
date: dateStr,
|
|
516
559
|
bucket: dateIso(bucketStart(commitDate, bucket))
|
|
517
560
|
};
|
|
518
|
-
|
|
561
|
+
runningTotals = { added: 0, deleted: 0, sum: 0 };
|
|
519
562
|
continue;
|
|
520
563
|
}
|
|
521
564
|
|
|
@@ -523,14 +566,29 @@ async function aggregate(repoPaths, startDate, endDate, bucket, authorEmails, au
|
|
|
523
566
|
const parts = line.split("\t");
|
|
524
567
|
if (parts.length !== 3) continue;
|
|
525
568
|
const [added, deleted] = parts;
|
|
526
|
-
if (added !== "-")
|
|
527
|
-
|
|
569
|
+
if (added !== "-") {
|
|
570
|
+
const addedValue = Number(added);
|
|
571
|
+
runningTotals.added += addedValue;
|
|
572
|
+
runningTotals.sum += addedValue;
|
|
573
|
+
}
|
|
574
|
+
if (deleted !== "-") {
|
|
575
|
+
const deletedValue = Number(deleted);
|
|
576
|
+
runningTotals.deleted += deletedValue;
|
|
577
|
+
runningTotals.sum += deletedValue;
|
|
578
|
+
}
|
|
528
579
|
}
|
|
529
580
|
|
|
530
581
|
if (current && !seen.has(current.sha)) {
|
|
531
582
|
seen.add(current.sha);
|
|
532
|
-
rawDaily
|
|
533
|
-
|
|
583
|
+
const daily = ensureTotals(rawDaily, current.date);
|
|
584
|
+
daily.added += runningTotals.added;
|
|
585
|
+
daily.deleted += runningTotals.deleted;
|
|
586
|
+
daily.sum += runningTotals.sum;
|
|
587
|
+
|
|
588
|
+
const bucketTotal = ensureTotals(bucketTotals, current.bucket);
|
|
589
|
+
bucketTotal.added += runningTotals.added;
|
|
590
|
+
bucketTotal.deleted += runningTotals.deleted;
|
|
591
|
+
bucketTotal.sum += runningTotals.sum;
|
|
534
592
|
}
|
|
535
593
|
}
|
|
536
594
|
|
|
@@ -539,15 +597,29 @@ async function aggregate(repoPaths, startDate, endDate, bucket, authorEmails, au
|
|
|
539
597
|
const last = bucketStart(endDate, bucket);
|
|
540
598
|
while (cursor <= last) {
|
|
541
599
|
const key = dateIso(cursor);
|
|
542
|
-
ordered.set(key, bucketTotals.get(key) || 0);
|
|
600
|
+
ordered.set(key, bucketTotals.get(key) || { added: 0, deleted: 0, sum: 0 });
|
|
543
601
|
if (bucket === "day") cursor = addDays(cursor, 1);
|
|
544
602
|
else if (bucket === "week") cursor = addDays(cursor, 7);
|
|
545
603
|
else cursor = new Date(Date.UTC(cursor.getUTCFullYear(), cursor.getUTCMonth() + 1, 1));
|
|
546
604
|
}
|
|
547
605
|
|
|
606
|
+
const bucketed = { added: {}, deleted: {}, sum: {} };
|
|
607
|
+
for (const [date, totals] of ordered.entries()) {
|
|
608
|
+
bucketed.added[date] = totals.added;
|
|
609
|
+
bucketed.deleted[date] = totals.deleted;
|
|
610
|
+
bucketed.sum[date] = totals.sum;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const daily = { added: {}, deleted: {}, sum: {} };
|
|
614
|
+
for (const [date, totals] of [...rawDaily.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
615
|
+
daily.added[date] = totals.added;
|
|
616
|
+
daily.deleted[date] = totals.deleted;
|
|
617
|
+
daily.sum[date] = totals.sum;
|
|
618
|
+
}
|
|
619
|
+
|
|
548
620
|
return {
|
|
549
|
-
|
|
550
|
-
|
|
621
|
+
bucketed,
|
|
622
|
+
daily
|
|
551
623
|
};
|
|
552
624
|
}
|
|
553
625
|
|
|
@@ -723,7 +795,7 @@ function formatCompact(value) {
|
|
|
723
795
|
}
|
|
724
796
|
|
|
725
797
|
function formatGrouped(value) {
|
|
726
|
-
return
|
|
798
|
+
return String(value).replace(/\B(?=(\d{3})+(?!\d))/g, ".");
|
|
727
799
|
}
|
|
728
800
|
|
|
729
801
|
function xLabel(date, bucket) {
|
|
@@ -732,9 +804,34 @@ function xLabel(date, bucket) {
|
|
|
732
804
|
return `${month} ${date.getUTCDate()}`;
|
|
733
805
|
}
|
|
734
806
|
|
|
735
|
-
function
|
|
736
|
-
|
|
737
|
-
|
|
807
|
+
function modeLabel(mode) {
|
|
808
|
+
return mode.replace(/\//g, " + ");
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
function sumValues(values) {
|
|
812
|
+
return values.reduce((sum, value) => sum + value, 0);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
function renderLegend(canvas, plotRight, top, seriesKeys) {
|
|
816
|
+
let cursorX = plotRight;
|
|
817
|
+
for (let i = seriesKeys.length - 1; i >= 0; i -= 1) {
|
|
818
|
+
const key = seriesKeys[i];
|
|
819
|
+
const style = SERIES_STYLES[key];
|
|
820
|
+
const label = style.legend;
|
|
821
|
+
const labelWidth = textWidth(label, 2);
|
|
822
|
+
const itemWidth = 24 + 10 + labelWidth;
|
|
823
|
+
cursorX -= itemWidth;
|
|
824
|
+
drawLine(canvas, cursorX, top, cursorX + 20, top, style.line, 4);
|
|
825
|
+
drawText(canvas, cursorX + 30, top - 8, label, TEXT, 2);
|
|
826
|
+
cursorX -= 28;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function renderChart(seriesByKey, mode, login, startDate, endDate, bucket, outputPath) {
|
|
831
|
+
const seriesKeys = MODES[mode];
|
|
832
|
+
const primarySeries = seriesByKey[seriesKeys[0]];
|
|
833
|
+
const dates = Object.keys(primarySeries).sort();
|
|
834
|
+
const valuesByKey = Object.fromEntries(seriesKeys.map((key) => [key, dates.map((date) => seriesByKey[key][date])]));
|
|
738
835
|
const width = 1800;
|
|
739
836
|
const height = 980;
|
|
740
837
|
const left = 130;
|
|
@@ -747,7 +844,8 @@ function renderChart(series, login, startDate, endDate, bucket, outputPath) {
|
|
|
747
844
|
const plotBottom = height - bottom;
|
|
748
845
|
const plotWidth = plotRight - plotLeft;
|
|
749
846
|
const plotHeight = plotBottom - plotTop;
|
|
750
|
-
const
|
|
847
|
+
const allValues = seriesKeys.flatMap((key) => valuesByKey[key]);
|
|
848
|
+
const maxValue = allValues.length ? Math.max(...allValues) : 0;
|
|
751
849
|
const upper = niceUpperBound(Math.max(maxValue, 10));
|
|
752
850
|
|
|
753
851
|
const canvas = createCanvas(width, height, BG);
|
|
@@ -763,15 +861,18 @@ function renderChart(series, login, startDate, endDate, bucket, outputPath) {
|
|
|
763
861
|
drawLine(canvas, plotLeft, plotBottom, plotRight, plotBottom, AXIS, 2);
|
|
764
862
|
drawLine(canvas, plotLeft, plotTop, plotLeft, plotBottom, AXIS, 2);
|
|
765
863
|
|
|
766
|
-
const
|
|
767
|
-
const
|
|
768
|
-
const
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
864
|
+
for (const key of seriesKeys) {
|
|
865
|
+
const style = SERIES_STYLES[key];
|
|
866
|
+
const points = valuesByKey[key].map((value, idx) => {
|
|
867
|
+
const x = plotLeft + Math.floor((idx * plotWidth) / Math.max(1, dates.length - 1));
|
|
868
|
+
const y = plotBottom - Math.floor((value / upper) * plotHeight);
|
|
869
|
+
return [x, y];
|
|
870
|
+
});
|
|
871
|
+
if (points.length && seriesKeys.length === 1) {
|
|
872
|
+
drawPolyFill(canvas, points, plotBottom, style.fill);
|
|
873
|
+
}
|
|
773
874
|
for (let i = 0; i < points.length - 1; i += 1) {
|
|
774
|
-
drawLine(canvas, points[i][0], points[i][1], points[i + 1][0], points[i + 1][1],
|
|
875
|
+
drawLine(canvas, points[i][0], points[i][1], points[i + 1][0], points[i + 1][1], style.line, 3);
|
|
775
876
|
}
|
|
776
877
|
}
|
|
777
878
|
|
|
@@ -783,7 +884,7 @@ function renderChart(series, login, startDate, endDate, bucket, outputPath) {
|
|
|
783
884
|
|
|
784
885
|
let drawnUntil = -1;
|
|
785
886
|
for (const idx of tickIndexes) {
|
|
786
|
-
const x = plotLeft + Math.floor((idx * plotWidth) / Math.max(1,
|
|
887
|
+
const x = plotLeft + Math.floor((idx * plotWidth) / Math.max(1, dates.length - 1));
|
|
787
888
|
const label = xLabel(parseDate(dates[idx]), bucket);
|
|
788
889
|
const widthPx = textWidth(label, 2);
|
|
789
890
|
const labelX = Math.max(plotLeft, Math.min(x - Math.floor(widthPx / 2), plotRight - widthPx));
|
|
@@ -793,16 +894,16 @@ function renderChart(series, login, startDate, endDate, bucket, outputPath) {
|
|
|
793
894
|
drawnUntil = labelX + widthPx;
|
|
794
895
|
}
|
|
795
896
|
|
|
796
|
-
drawText(canvas, 24, 24, `${login}
|
|
897
|
+
drawText(canvas, 24, 24, `${login} ${modeLabel(mode)} per ${bucket}`, TEXT, 5);
|
|
797
898
|
drawText(canvas, 24, 74, `${dateIso(startDate)} to ${dateIso(endDate)}`, AXIS, 3);
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
);
|
|
899
|
+
const summary = seriesKeys
|
|
900
|
+
.map((key) => {
|
|
901
|
+
const values = valuesByKey[key];
|
|
902
|
+
return `${SERIES_STYLES[key].legend}: ${formatGrouped(sumValues(values))}/${formatGrouped(values.length ? Math.max(...values) : 0)}`;
|
|
903
|
+
})
|
|
904
|
+
.join(" ");
|
|
905
|
+
drawText(canvas, 24, 104, `Total/Peak: ${summary}`, AXIS, 3);
|
|
906
|
+
if (seriesKeys.length > 1) renderLegend(canvas, plotRight, 56, seriesKeys);
|
|
806
907
|
|
|
807
908
|
savePng(canvas, outputPath);
|
|
808
909
|
}
|
|
@@ -849,7 +950,7 @@ async function main() {
|
|
|
849
950
|
}
|
|
850
951
|
|
|
851
952
|
logStep(`Fetching commits from ${repoPaths.length} repos...`);
|
|
852
|
-
const {
|
|
953
|
+
const { bucketed, daily } = await aggregate(
|
|
853
954
|
repoPaths,
|
|
854
955
|
startDate,
|
|
855
956
|
endDate,
|
|
@@ -858,10 +959,14 @@ async function main() {
|
|
|
858
959
|
authorNames
|
|
859
960
|
);
|
|
860
961
|
|
|
861
|
-
logStep(
|
|
862
|
-
|
|
962
|
+
logStep(
|
|
963
|
+
`Crunching numbers for ${args.bucket} buckets from ${dateIso(startDate)} to ${dateIso(endDate)} in ${args.mode} mode...`
|
|
964
|
+
);
|
|
965
|
+
renderChart(bucketed, args.mode, login, startDate, endDate, args.bucket, output);
|
|
863
966
|
|
|
864
|
-
const values = Object.values(
|
|
967
|
+
const values = Object.values(bucketed.sum);
|
|
968
|
+
const addedValues = Object.values(bucketed.added);
|
|
969
|
+
const deletedValues = Object.values(bucketed.deleted);
|
|
865
970
|
fs.writeFileSync(
|
|
866
971
|
jsonOutput,
|
|
867
972
|
JSON.stringify(
|
|
@@ -870,16 +975,27 @@ async function main() {
|
|
|
870
975
|
from: dateIso(startDate),
|
|
871
976
|
to: dateIso(endDate),
|
|
872
977
|
bucket: args.bucket,
|
|
978
|
+
mode: args.mode,
|
|
873
979
|
author_emails: authorEmails,
|
|
874
980
|
author_names: authorNames,
|
|
875
981
|
root_used: root,
|
|
876
982
|
searched_roots: searchedRoots,
|
|
877
983
|
local_repositories_used: repoPaths.map(([name]) => name),
|
|
878
984
|
missing_repositories: missing,
|
|
985
|
+
total_lines_added: sumValues(addedValues),
|
|
986
|
+
peak_lines_added: addedValues.length ? Math.max(...addedValues) : 0,
|
|
987
|
+
total_lines_deleted: sumValues(deletedValues),
|
|
988
|
+
peak_lines_deleted: deletedValues.length ? Math.max(...deletedValues) : 0,
|
|
879
989
|
total_lines_changed: values.reduce((sum, value) => sum + value, 0),
|
|
880
990
|
peak_lines_changed: values.length ? Math.max(...values) : 0,
|
|
881
|
-
|
|
882
|
-
|
|
991
|
+
bucketed_lines_added: bucketed.added,
|
|
992
|
+
bucketed_lines_deleted: bucketed.deleted,
|
|
993
|
+
bucketed_lines_changed: bucketed.sum,
|
|
994
|
+
bucketed_series: bucketed,
|
|
995
|
+
daily_lines_added: daily.added,
|
|
996
|
+
daily_lines_deleted: daily.deleted,
|
|
997
|
+
daily_lines_changed: daily.sum,
|
|
998
|
+
daily_series: daily
|
|
883
999
|
},
|
|
884
1000
|
null,
|
|
885
1001
|
2
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "locmeter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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,
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"bin",
|
|
19
19
|
"README.md",
|
|
20
|
-
"examples/jojo-weekly.png"
|
|
20
|
+
"examples/jojo-weekly.png",
|
|
21
|
+
"examples/jojo-weekly-added-deleted-sum.png"
|
|
21
22
|
],
|
|
22
23
|
"keywords": [
|
|
23
24
|
"cli",
|