@stats-forge/github-stats-forge-core 0.1.0 → 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.
Files changed (142) hide show
  1. package/build/api/contributed-to.d.ts +23 -0
  2. package/build/api/contributed-to.d.ts.map +1 -0
  3. package/build/api/contributed-to.js +43 -0
  4. package/build/api/gist.d.ts +8 -15
  5. package/build/api/gist.d.ts.map +1 -1
  6. package/build/api/gist.js +14 -29
  7. package/build/api/handler.d.ts +16 -0
  8. package/build/api/handler.d.ts.map +1 -0
  9. package/build/api/handler.js +29 -0
  10. package/build/api/index.d.ts +1 -0
  11. package/build/api/index.d.ts.map +1 -1
  12. package/build/api/index.js +1 -0
  13. package/build/api/params.d.ts +11 -5
  14. package/build/api/params.d.ts.map +1 -1
  15. package/build/api/params.js +26 -6
  16. package/build/api/pin.d.ts +11 -15
  17. package/build/api/pin.d.ts.map +1 -1
  18. package/build/api/pin.js +34 -46
  19. package/build/api/stats.d.ts +15 -22
  20. package/build/api/stats.d.ts.map +1 -1
  21. package/build/api/stats.js +59 -72
  22. package/build/api/top-langs.d.ts +11 -22
  23. package/build/api/top-langs.d.ts.map +1 -1
  24. package/build/api/top-langs.js +34 -51
  25. package/build/api/wakatime.d.ts +10 -24
  26. package/build/api/wakatime.d.ts.map +1 -1
  27. package/build/api/wakatime.js +27 -47
  28. package/build/cards/contributed-to.d.ts +19 -0
  29. package/build/cards/contributed-to.d.ts.map +1 -0
  30. package/build/cards/contributed-to.js +242 -0
  31. package/build/cards/gist.d.ts +1 -0
  32. package/build/cards/gist.d.ts.map +1 -1
  33. package/build/cards/gist.js +13 -9
  34. package/build/cards/repo.d.ts +12 -1
  35. package/build/cards/repo.d.ts.map +1 -1
  36. package/build/cards/repo.js +40 -18
  37. package/build/cards/stats.d.ts +15 -3
  38. package/build/cards/stats.d.ts.map +1 -1
  39. package/build/cards/stats.js +91 -76
  40. package/build/cards/top-languages.d.ts +13 -2
  41. package/build/cards/top-languages.d.ts.map +1 -1
  42. package/build/cards/top-languages.js +26 -9
  43. package/build/cards/wakatime.d.ts +13 -2
  44. package/build/cards/wakatime.d.ts.map +1 -1
  45. package/build/cards/wakatime.js +22 -9
  46. package/build/common/Card.d.ts +9 -0
  47. package/build/common/Card.d.ts.map +1 -1
  48. package/build/common/Card.js +44 -9
  49. package/build/common/I18n.d.ts +1 -1
  50. package/build/common/I18n.d.ts.map +1 -1
  51. package/build/common/I18n.js +4 -2
  52. package/build/common/brand.d.ts +76 -0
  53. package/build/common/brand.d.ts.map +1 -0
  54. package/build/common/brand.js +73 -0
  55. package/build/common/constants.d.ts +3 -1
  56. package/build/common/constants.d.ts.map +1 -1
  57. package/build/common/constants.js +3 -1
  58. package/build/common/date.d.ts +50 -3
  59. package/build/common/date.d.ts.map +1 -1
  60. package/build/common/date.js +104 -2
  61. package/build/common/icons.d.ts +4 -2
  62. package/build/common/icons.d.ts.map +1 -1
  63. package/build/common/icons.js +10 -5
  64. package/build/common/ops.d.ts +8 -3
  65. package/build/common/ops.d.ts.map +1 -1
  66. package/build/common/ops.js +8 -14
  67. package/build/common/render.d.ts +7 -1
  68. package/build/common/render.d.ts.map +1 -1
  69. package/build/common/render.js +60 -58
  70. package/build/fetchers/contributed-to.d.ts +62 -0
  71. package/build/fetchers/contributed-to.d.ts.map +1 -0
  72. package/build/fetchers/contributed-to.js +184 -0
  73. package/build/fetchers/graphql-error.d.ts +13 -0
  74. package/build/fetchers/graphql-error.d.ts.map +1 -0
  75. package/build/fetchers/graphql-error.js +29 -0
  76. package/build/fetchers/index.d.ts +2 -1
  77. package/build/fetchers/index.d.ts.map +1 -1
  78. package/build/fetchers/index.js +1 -0
  79. package/build/fetchers/stats.d.ts +14 -8
  80. package/build/fetchers/stats.d.ts.map +1 -1
  81. package/build/fetchers/stats.js +72 -195
  82. package/build/fetchers/top-languages.d.ts.map +1 -1
  83. package/build/fetchers/top-languages.js +3 -18
  84. package/build/fetchers/types.d.ts +24 -0
  85. package/build/fetchers/types.d.ts.map +1 -1
  86. package/build/graphql/contributionsCollection.d.ts +9 -0
  87. package/build/graphql/contributionsCollection.d.ts.map +1 -0
  88. package/build/graphql/contributionsCollection.js +12 -0
  89. package/build/graphql/contributionsDocument.d.ts +5 -5
  90. package/build/graphql/contributionsDocument.d.ts.map +1 -1
  91. package/build/graphql/contributionsDocument.js +8 -15
  92. package/build/graphql/generated/contributed-to.d.ts +22 -0
  93. package/build/graphql/generated/contributed-to.d.ts.map +1 -0
  94. package/build/graphql/generated/contributed-to.js +11 -0
  95. package/build/graphql/generated/stats.d.ts +13 -1
  96. package/build/graphql/generated/stats.d.ts.map +1 -1
  97. package/build/graphql/generated/stats.js +2 -2
  98. package/build/graphql/graphqlDocument.d.ts +1 -1
  99. package/build/graphql/reposContributedToDocument.d.ts.map +1 -1
  100. package/build/graphql/reposContributedToDocument.js +11 -5
  101. package/build/translations.d.ts +64 -1
  102. package/build/translations.d.ts.map +1 -1
  103. package/build/translations.js +65 -1
  104. package/package.json +1 -1
  105. package/src/api/contributed-to.ts +84 -0
  106. package/src/api/gist.ts +20 -37
  107. package/src/api/handler.ts +42 -0
  108. package/src/api/index.ts +1 -0
  109. package/src/api/params.ts +48 -9
  110. package/src/api/pin.ts +33 -45
  111. package/src/api/stats.ts +78 -89
  112. package/src/api/top-langs.ts +34 -56
  113. package/src/api/wakatime.ts +33 -54
  114. package/src/cards/contributed-to.ts +373 -0
  115. package/src/cards/gist.ts +17 -8
  116. package/src/cards/repo.ts +44 -20
  117. package/src/cards/stats.ts +119 -90
  118. package/src/cards/top-languages.ts +27 -18
  119. package/src/cards/wakatime.ts +23 -13
  120. package/src/common/Card.ts +56 -11
  121. package/src/common/I18n.ts +4 -2
  122. package/src/common/brand.ts +109 -0
  123. package/src/common/constants.ts +4 -1
  124. package/src/common/date.ts +135 -3
  125. package/src/common/icons.ts +10 -5
  126. package/src/common/ops.ts +10 -19
  127. package/src/common/render.ts +65 -62
  128. package/src/fetchers/contributed-to.ts +271 -0
  129. package/src/fetchers/graphql-error.ts +36 -0
  130. package/src/fetchers/index.ts +3 -0
  131. package/src/fetchers/stats.ts +123 -274
  132. package/src/fetchers/top-languages.ts +5 -19
  133. package/src/fetchers/types.ts +26 -0
  134. package/src/graphql/contributionsCollection.ts +18 -0
  135. package/src/graphql/contributionsDocument.ts +11 -18
  136. package/src/graphql/generated/contributed-to.ts +25 -0
  137. package/src/graphql/generated/stats.ts +19 -6
  138. package/src/graphql/graphqlDocument.ts +1 -1
  139. package/src/graphql/queries/contributed-to.graphql +8 -0
  140. package/src/graphql/queries/stats.graphql +14 -2
  141. package/src/graphql/reposContributedToDocument.ts +11 -9
  142. package/src/translations.ts +77 -0
@@ -34,14 +34,9 @@ const parseArray = (str) => {
34
34
  /**
35
35
  * Clamp the given number between the given range.
36
36
  *
37
- * @returns The clamped number.
37
+ * @returns The clamped number; `min` for `NaN`.
38
38
  */
39
- const clampValue = (number, min, max) => {
40
- if (Number.isNaN(Number.parseInt(String(number), 10))) {
41
- return min;
42
- }
43
- return Math.max(min, Math.min(Number(number), max));
44
- };
39
+ const clampValue = (number, min, max) => Number.isNaN(number) ? min : Math.max(min, Math.min(number, max));
45
40
  /**
46
41
  * Lowercase and trim string.
47
42
  *
@@ -94,11 +89,10 @@ const parseOwnerAffiliations = (affiliations) => {
94
89
  }
95
90
  return normalized;
96
91
  };
97
- const buildSearchFilter = (repos = [], owners = []) => {
98
- const repoFilter = Array.isArray(repos) && repos.length > 0 ? repos.map((repo) => `repo:${repo} `).join('') : '';
99
- const orgFilter = Array.isArray(owners) && owners.length > 0
100
- ? owners.map((owner) => `owner:${owner} `).join('')
101
- : '';
102
- return repoFilter + orgFilter;
103
- };
92
+ /**
93
+ * The qualifiers that scope a REST search to repositories and owners.
94
+ *
95
+ * @returns e.g. `repo:a/b owner:c `, each qualifier followed by a space; empty when both lists are.
96
+ */
97
+ const buildSearchFilter = (repos, owners) => [...repos.map((repo) => `repo:${repo} `), ...owners.map((owner) => `owner:${owner} `)].join('');
104
98
  export { parseBoolean, parseArray, clampValue, lowercaseTrim, chunkArray, parseEmojis, parseOwnerAffiliations, buildSearchFilter, };
@@ -64,6 +64,12 @@ declare const wrappedTextStyles: (color: string) => Declarations;
64
64
  * @returns Icon with label SVG object, or nothing when the label is not positive.
65
65
  */
66
66
  declare const iconWithLabel: (icon: Child, label: number | string, testid: string, iconSize: number) => Child;
67
+ /**
68
+ * How a stat value is written.
69
+ * Not validated at the boundary: anything but `long` reads as `short`,
70
+ * so an unknown value falls back rather than failing.
71
+ */
72
+ declare const NUMBER_FORMATS: readonly ['short', 'long'];
67
73
  /**
68
74
  * Create a labelled stat text item
69
75
  * (a label and its value, optionally with an icon and link).
@@ -138,5 +144,5 @@ declare const wrapTextMultiline: (text: string, width: number, fontSize: number,
138
144
  * @returns Estimated line count, at least 1, at most `maxLines`.
139
145
  */
140
146
  declare const countWrappedLines: (text: string, fontSize: number, maxWidth: number, maxLines: number) => number;
141
- export { renderError, createLanguageNode, createProgressNode, createTextNode, iconWithLabel, flexLayout, measureText, splitWrappedText, wrapTextMultiline, countWrappedLines, wrappedTextNode, wrappedTextStyles, };
147
+ export { NUMBER_FORMATS, renderError, createLanguageNode, createProgressNode, createTextNode, iconWithLabel, flexLayout, measureText, splitWrappedText, wrapTextMultiline, countWrappedLines, wrappedTextNode, wrappedTextStyles, };
142
148
  //# sourceMappingURL=render.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/common/render.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/common/render.ts"],"names":[],"mappings":"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"}
@@ -169,6 +169,12 @@ const iconWithLabel = (icon, label, testid, iconSize) => {
169
169
  gap: 20,
170
170
  });
171
171
  };
172
+ /**
173
+ * How a stat value is written.
174
+ * Not validated at the boundary: anything but `long` reads as `short`,
175
+ * so an unknown value falls back rather than failing.
176
+ */
177
+ const NUMBER_FORMATS = ['short', 'long'];
172
178
  /**
173
179
  * Create a labelled stat text item
174
180
  * (a label and its value, optionally with an icon and link).
@@ -256,8 +262,8 @@ const renderError = ({ message, secondaryMessage = '', renderOptions = {}, }) =>
256
262
  fill: String(bgColor),
257
263
  xmlns: 'http://www.w3.org/2000/svg',
258
264
  role: 'img',
259
- 'aria-labelledby': 'titleId descId',
260
- }, el('title', { id: 'titleId' }, message), el('desc', { id: 'descId' }, secondaryMessage), style(rule('.text', { font: "600 16px 'Segoe UI', Ubuntu, Sans-Serif", fill: titleColor }), rule('.small', { font: "600 12px 'Segoe UI', Ubuntu, Sans-Serif", fill: textColor }), rule('.gray', { fill: '#858585' })), el('rect', {
265
+ 'aria-labelledby': 'title-id desc-id',
266
+ }, el('title', { id: 'title-id' }, message), el('desc', { id: 'desc-id' }, secondaryMessage), style(rule('.text', { font: "600 16px 'Segoe UI', Ubuntu, Sans-Serif", fill: titleColor }), rule('.small', { font: "600 12px 'Segoe UI', Ubuntu, Sans-Serif", fill: textColor }), rule('.gray', { fill: '#858585' })), el('rect', {
261
267
  x: 0.5,
262
268
  y: 0.5,
263
269
  width: ERROR_CARD_LENGTH - 1,
@@ -267,6 +273,49 @@ const renderError = ({ message, secondaryMessage = '', renderOptions = {}, }) =>
267
273
  stroke: borderColor,
268
274
  }), el('text', { x: 25, y: 45, class: 'text' }, `Something went wrong!${hint}`), el('text', { 'data-testid': 'message', x: 25, y: 55, class: 'text small' }, el('tspan', { x: 25, dy: 18 }, message), el('tspan', { x: 25, dy: 18, class: 'gray' }, secondaryMessage))));
269
275
  };
276
+ /** Advance of each ASCII code point, as a fraction of the font size. */
277
+ // prettier-ignore
278
+ const CHAR_WIDTHS = [
279
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
280
+ 0, 0, 0, 0, 0, 0, 0, 0.2733333110809326, 0.28499999046325686,
281
+ 0.39166667461395266, 0.5900000095367431, 0.5383333206176758,
282
+ 0.8183333396911621, 0.8, 0.22999999523162842, 0.30166666507720946,
283
+ 0.30166666507720946, 0.41666665077209475, 0.6833333492279052,
284
+ 0.21666667461395264, 0.4, 0.21666667461395264, 0.3900000095367432,
285
+ 0.5383333206176758, 0.5383333206176758, 0.5383333206176758,
286
+ 0.5383333206176758, 0.5383333206176758, 0.5383333206176758,
287
+ 0.5383333206176758, 0.5383333206176758, 0.5383333206176758,
288
+ 0.5383333206176758, 0.21666667461395264, 0.21666667461395264,
289
+ 0.6833333492279052, 0.6833333492279052, 0.6833333492279052,
290
+ 0.4483333110809326, 0.9550000190734863, 0.6449999809265137,
291
+ 0.5733333110809327, 0.6183333396911621, 0.7016666889190674,
292
+ 0.5066666603088379, 0.48833332061767576, 0.6866666793823242,
293
+ 0.7099999904632568, 0.26666667461395266, 0.35666666030883787,
294
+ 0.5800000190734863, 0.4699999809265137, 0.8983333587646485,
295
+ 0.7483333110809326, 0.753333330154419, 0.5599999904632569,
296
+ 0.753333330154419, 0.5983333110809326, 0.5316666603088379,
297
+ 0.5233333110809326, 0.6866666793823242, 0.621666669845581,
298
+ 0.9333333015441895, 0.5900000095367431, 0.553333330154419,
299
+ 0.5699999809265137, 0.30166666507720946, 0.37833333015441895,
300
+ 0.30166666507720946, 0.6833333492279052, 0.41500000953674315,
301
+ 0.26833333969116213, 0.5083333492279053, 0.5883333206176757,
302
+ 0.4616666793823242, 0.5883333206176757, 0.5233333110809326,
303
+ 0.3133333444595337, 0.5883333206176757, 0.5666666507720948,
304
+ 0.24166667461395264, 0.24166667461395264, 0.49666666984558105,
305
+ 0.24166667461395264, 0.8616666793823242, 0.5666666507720948,
306
+ 0.5866666793823242, 0.5883333206176757, 0.5883333206176757,
307
+ 0.3483333349227905, 0.425, 0.33833334445953367, 0.5666666507720948,
308
+ 0.4783333301544189, 0.7233333110809326, 0.45833334922790525,
309
+ 0.4833333492279053, 0.45166668891906736, 0.30166666507720946,
310
+ 0.24000000953674316, 0.30166666507720946, 0.6833333492279052,
311
+ ];
312
+ /** Advance of a code point the table does not cover. */
313
+ const AVERAGE_CHAR_WIDTH = 0.5131403493881227;
314
+ /**
315
+ * Full-width glyphs: U+3000–U+9FFF (CJK symbols and punctuation, Hiragana, Katakana,
316
+ * CJK Unified Ideographs incl. Extension A) plus U+FF00–U+FFEF (halfwidth and fullwidth forms).
317
+ */
318
+ const FULL_WIDTH_PATTERN = /[\u3000-\u9FFF\uFF00-\uFFEF]/;
270
319
  /**
271
320
  * Retrieve text length based on Segoe UI font.
272
321
  *
@@ -274,61 +323,14 @@ const renderError = ({ message, secondaryMessage = '', renderOptions = {}, }) =>
274
323
  *
275
324
  * @returns Text length.
276
325
  */
277
- const measureText = (str, fontSize = 10) => {
278
- // prettier-ignore
279
- const widths = [
280
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
281
- 0, 0, 0, 0, 0, 0, 0, 0.2733333110809326, 0.28499999046325686,
282
- 0.39166667461395266, 0.5900000095367431, 0.5383333206176758,
283
- 0.8183333396911621, 0.8, 0.22999999523162842, 0.30166666507720946,
284
- 0.30166666507720946, 0.41666665077209475, 0.6833333492279052,
285
- 0.21666667461395264, 0.4, 0.21666667461395264, 0.3900000095367432,
286
- 0.5383333206176758, 0.5383333206176758, 0.5383333206176758,
287
- 0.5383333206176758, 0.5383333206176758, 0.5383333206176758,
288
- 0.5383333206176758, 0.5383333206176758, 0.5383333206176758,
289
- 0.5383333206176758, 0.21666667461395264, 0.21666667461395264,
290
- 0.6833333492279052, 0.6833333492279052, 0.6833333492279052,
291
- 0.4483333110809326, 0.9550000190734863, 0.6449999809265137,
292
- 0.5733333110809327, 0.6183333396911621, 0.7016666889190674,
293
- 0.5066666603088379, 0.48833332061767576, 0.6866666793823242,
294
- 0.7099999904632568, 0.26666667461395266, 0.35666666030883787,
295
- 0.5800000190734863, 0.4699999809265137, 0.8983333587646485,
296
- 0.7483333110809326, 0.753333330154419, 0.5599999904632569,
297
- 0.753333330154419, 0.5983333110809326, 0.5316666603088379,
298
- 0.5233333110809326, 0.6866666793823242, 0.621666669845581,
299
- 0.9333333015441895, 0.5900000095367431, 0.553333330154419,
300
- 0.5699999809265137, 0.30166666507720946, 0.37833333015441895,
301
- 0.30166666507720946, 0.6833333492279052, 0.41500000953674315,
302
- 0.26833333969116213, 0.5083333492279053, 0.5883333206176757,
303
- 0.4616666793823242, 0.5883333206176757, 0.5233333110809326,
304
- 0.3133333444595337, 0.5883333206176757, 0.5666666507720948,
305
- 0.24166667461395264, 0.24166667461395264, 0.49666666984558105,
306
- 0.24166667461395264, 0.8616666793823242, 0.5666666507720948,
307
- 0.5866666793823242, 0.5883333206176757, 0.5883333206176757,
308
- 0.3483333349227905, 0.425, 0.33833334445953367, 0.5666666507720948,
309
- 0.4783333301544189, 0.7233333110809326, 0.45833334922790525,
310
- 0.4833333492279053, 0.45166668891906736, 0.30166666507720946,
311
- 0.24000000953674316, 0.30166666507720946, 0.6833333492279052,
312
- ];
313
- const avg = 0.5131403493881227;
314
- // CJK character range: U+3000–U+9FFF (CJK Symbols/Punctuation, Hiragana,
315
- // Katakana, CJK Unified Ideographs incl. Extension A) plus U+FF00–U+FFEF
316
- // (Halfwidth/Fullwidth Forms — fullwidth ASCII, fullwidth punctuation).
317
- const cjkRange = /[\u3000-\u9FFF\uFF00-\uFFEF]/;
318
- return ([...str]
319
- .map((char) => {
320
- if (cjkRange.test(char) || char === '\u3000') {
321
- // CJK glyphs and U+3000 IDEOGRAPHIC SPACE are full-width by default;
322
- return 1;
323
- }
324
- const code = char.codePointAt(0) ?? 0;
325
- if (code < widths.length) {
326
- return widths[code] ?? avg;
327
- }
328
- return avg;
329
- })
330
- .reduce((cur, acc) => acc + cur) * fontSize);
331
- };
326
+ const measureText = (str, fontSize = 10) => [...str]
327
+ .map((char) => {
328
+ if (FULL_WIDTH_PATTERN.test(char)) {
329
+ return 1;
330
+ }
331
+ return CHAR_WIDTHS[char.codePointAt(0) ?? 0] ?? AVERAGE_CHAR_WIDTH;
332
+ })
333
+ .reduce((total, width) => total + width, 0) * fontSize;
332
334
  /**
333
335
  * Split text into the lines it would wrap to when laid out greedily at the
334
336
  * given font size inside a box of width `maxWidth`. Uses `measureText` so the
@@ -435,4 +437,4 @@ const wrapTextMultiline = (text, width, fontSize, maxLines = 3) => {
435
437
  * @returns Estimated line count, at least 1, at most `maxLines`.
436
438
  */
437
439
  const countWrappedLines = (text, fontSize, maxWidth, maxLines) => Math.min(Math.max(1, splitWrappedText(text, fontSize, maxWidth).length), maxLines);
438
- export { renderError, createLanguageNode, createProgressNode, createTextNode, iconWithLabel, flexLayout, measureText, splitWrappedText, wrapTextMultiline, countWrappedLines, wrappedTextNode, wrappedTextStyles, };
440
+ export { NUMBER_FORMATS, renderError, createLanguageNode, createProgressNode, createTextNode, iconWithLabel, flexLayout, measureText, splitWrappedText, wrapTextMultiline, countWrappedLines, wrappedTextNode, wrappedTextStyles, };
@@ -0,0 +1,62 @@
1
+ import type { CardConfig } from '../common/config.ts';
2
+ import type { GitHubDateRange } from '../common/date.ts';
3
+ import type { ContributedToData } from './types.ts';
4
+ /** What one repository accumulated over the walk. */
5
+ interface RepoContributions {
6
+ /**
7
+ * Commit-days plus issues and pull requests opened.
8
+ * `CreatedCommitContribution` is one node per repository per day,
9
+ * so its `totalCount` counts days with commits, not commits —
10
+ * true commit counts would mean fetching the connection's nodes.
11
+ */
12
+ contributions: number;
13
+ /** Every year the user contributed to it. */
14
+ years: Set<number>;
15
+ }
16
+ /**
17
+ * The repositories a user contributed to across every contribution year, with what each one got.
18
+ *
19
+ * `repositoriesContributedTo` spans at most one year,
20
+ * so every range is fetched as an aliased `contributionsCollection(from, to)` in one request and the repos merged.
21
+ * A range returning `MAX_REPOSITORIES_LIMIT` results may have more,
22
+ * so it is halved and requeried in the next round — a split stays inside its own calendar year,
23
+ * which is what makes `range.from`'s year the contribution's year.
24
+ *
25
+ * Whether private contributions are included depends on the used PAT.
26
+ *
27
+ * @returns Every repository found, keyed by `nameWithOwner`.
28
+ */
29
+ declare const fetchReposContributedTo: (
30
+ /** `nameWithOwner` uses GitHub's casing, which the query-string username need not match */
31
+ canonicalUsername: string,
32
+ /** One per calendar year, so a contribution's year is the one its range starts in. */
33
+ ranges: Array<GitHubDateRange>, includeOwnRepos: boolean, config: CardConfig) => Promise<Map<string, RepoContributions>>;
34
+ /**
35
+ * Rank the repositories a user has contributed to, most contributions first.
36
+ *
37
+ * `from` and `to` narrow the walk to the range they name,
38
+ * so the ranking, the totals and the year marks all cover that range and nothing else.
39
+ * A range the account contributed in no year of draws an empty card rather than an error.
40
+ *
41
+ * Ties keep the walk's order, which is chronological:
42
+ * the repository first seen in the earliest range comes first.
43
+ *
44
+ * An excluded repository is dropped before the total is counted,
45
+ * so the footer's "top n of m" never counts one the card was told not to show.
46
+ *
47
+ * @returns The ranked slice, and the totals it was taken from.
48
+ */
49
+ declare const fetchContributedTo: ({ username, include_own_repos, repos_count, exclude_repo, from, to, }: {
50
+ username: string | undefined;
51
+ include_own_repos?: boolean | undefined;
52
+ /** Rows to rank; out of range or unparsed, the default stands. */
53
+ repos_count?: number | undefined;
54
+ /** Each entry is an `owner/name` or a bare `name`. */
55
+ exclude_repo?: Array<string>;
56
+ /** Start of the range to walk; as early as GitHub goes when absent. */
57
+ from?: Date | undefined;
58
+ /** End of that range. */
59
+ to?: Date | undefined;
60
+ }, config: CardConfig) => Promise<ContributedToData>;
61
+ export { fetchContributedTo, fetchReposContributedTo };
62
+ //# sourceMappingURL=contributed-to.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contributed-to.d.ts","sourceRoot":"","sources":["../../src/fetchers/contributed-to.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAczD,OAAO,KAAK,EAAE,iBAAiB,EAAmB,MAAM,YAAY,CAAC;AAsBrE,qDAAqD;AACrD,UAAU,iBAAiB;IACzB;;;;;OAKG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,6CAA6C;IAC7C,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACpB;AAID;;;;;;;;;;;;GAYG;AACH,QAAA,MAAM,uBAAuB;AAC3B,2FAA2F;mBACxE,MAAM;AACzB,sFAAsF;QAC9E,KAAK,CAAC,eAAe,CAAC,mBACb,OAAO,UAChB,UAAU,KACjB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CA6FxC,CAAC;AAmBF;;;;;;;;;;;;;;GAcG;AACH,QAAA,MAAM,kBAAkB,0EAQnB;IACD,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,iBAAiB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACxC,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,sDAAsD;IACtD,YAAY,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,uEAAuE;IACvE,IAAI,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IACxB,yBAAyB;IACzB,EAAE,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;CACvB,UACO,UAAU,KACjB,OAAO,CAAC,iBAAiB,CAgD3B,CAAC;AAEF,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,CAAC"}
@@ -0,0 +1,184 @@
1
+ import { toContributionRanges, toRange } from '../common/date.js';
2
+ import { CardError, USER_NOT_FOUND } from '../common/error.js';
3
+ import { createGraphQLFetcher } from '../common/http.js';
4
+ import { logger } from '../common/log.js';
5
+ import { chunkArray, clampValue } from '../common/ops.js';
6
+ import { retryer } from '../common/retryer.js';
7
+ import { UserContributionYearsDocument } from '../graphql/generated/contributed-to.js';
8
+ import { MAX_REPOSITORIES_LIMIT, buildReposContributedToDocument, } from '../graphql/reposContributedToDocument.js';
9
+ import { graphqlError } from './graphql-error.js';
10
+ const MS_PER_DAY = 24 * 60 * 60 * 1000;
11
+ /**
12
+ * Ranges per request.
13
+ * Each costs roughly `4 * MAX_REPOSITORIES_LIMIT` nodes,
14
+ * so an unchunked round of a heavily split account would breach GitHub's 500k node ceiling
15
+ * and lose the ranges that had already resolved along with it.
16
+ */
17
+ const MAX_RANGES_PER_REQUEST = 100;
18
+ const REPOS_CONTRIBUTED_TO_ERROR = 'Something went wrong while trying to retrieve the repository contributions data using the GraphQL API.';
19
+ /** Rows the card ranks when the query names no count, and the most it will rank. */
20
+ const DEFAULT_REPOS_COUNT = 5;
21
+ const MAX_REPOS_COUNT = 20;
22
+ const CONTRIBUTION_YEARS_ERROR = 'Something went wrong while trying to retrieve the contribution years using the GraphQL API.';
23
+ const yearsFetcher = createGraphQLFetcher(UserContributionYearsDocument, 'bearer');
24
+ /**
25
+ * The repositories a user contributed to across every contribution year, with what each one got.
26
+ *
27
+ * `repositoriesContributedTo` spans at most one year,
28
+ * so every range is fetched as an aliased `contributionsCollection(from, to)` in one request and the repos merged.
29
+ * A range returning `MAX_REPOSITORIES_LIMIT` results may have more,
30
+ * so it is halved and requeried in the next round — a split stays inside its own calendar year,
31
+ * which is what makes `range.from`'s year the contribution's year.
32
+ *
33
+ * Whether private contributions are included depends on the used PAT.
34
+ *
35
+ * @returns Every repository found, keyed by `nameWithOwner`.
36
+ */
37
+ const fetchReposContributedTo = async (
38
+ /** `nameWithOwner` uses GitHub's casing, which the query-string username need not match */
39
+ canonicalUsername,
40
+ /** One per calendar year, so a contribution's year is the one its range starts in. */
41
+ ranges, includeOwnRepos, config) => {
42
+ const repos = new Map();
43
+ let pending = ranges;
44
+ while (pending.length > 0) {
45
+ const nextPending = [];
46
+ for (const chunk of chunkArray(pending, MAX_RANGES_PER_REQUEST)) {
47
+ const chunkFetcher = createGraphQLFetcher(buildReposContributedToDocument(chunk, includeOwnRepos), 'bearer');
48
+ const res = await retryer(chunkFetcher, { login: canonicalUsername, maxRepositories: MAX_REPOSITORIES_LIMIT }, config);
49
+ if (res.data.errors) {
50
+ throw graphqlError(res.data.errors, res.statusText, REPOS_CONTRIBUTED_TO_ERROR);
51
+ }
52
+ const { user } = res.data.data;
53
+ if (!user) {
54
+ throw new CardError(REPOS_CONTRIBUTED_TO_ERROR, { code: 'upstream' });
55
+ }
56
+ for (const [index, range] of chunk.entries()) {
57
+ const rangeResponse = user[`range_${index}`];
58
+ if (!rangeResponse) {
59
+ throw new CardError(REPOS_CONTRIBUTED_TO_ERROR, {
60
+ code: 'upstream',
61
+ });
62
+ }
63
+ const counted = [
64
+ rangeResponse.commitContributionsByRepository,
65
+ rangeResponse.issueContributionsByRepository,
66
+ rangeResponse.pullRequestContributionsByRepository,
67
+ ];
68
+ // creating a repository is one contribution, and the field carries no count of its own
69
+ const created = (rangeResponse.repositoryContributions?.nodes ?? [])
70
+ .filter((node) => node !== null)
71
+ .map((node) => ({ repository: node.repository, contributions: { totalCount: 1 } }));
72
+ const lists = [...counted, created];
73
+ const isSaturated = lists.some((list) => list.length >= MAX_REPOSITORIES_LIMIT);
74
+ const rangeDays = Math.round((range.to.getTime() - range.from.getTime()) / MS_PER_DAY);
75
+ // a range of 1 day or less can't be split any further
76
+ if (isSaturated && rangeDays >= 2) {
77
+ // every `from` sits on UTC midnight, so the split lands on a day boundary too
78
+ const mid = new Date(range.from.getTime() + Math.floor(rangeDays / 2) * MS_PER_DAY);
79
+ // GitHub only reads the date portion,
80
+ // so the first half ends 1 second before `mid` to keep the halves from sharing a day
81
+ nextPending.push({ from: range.from, to: new Date(mid.getTime() - 1000) }, { from: mid, to: range.to });
82
+ continue;
83
+ }
84
+ if (isSaturated) {
85
+ logger.log(`Range ${range.from.toISOString()} - ${range.to.toISOString()} is saturated but cannot be split further.`);
86
+ }
87
+ const year = range.from.getUTCFullYear();
88
+ for (const { repository, contributions } of lists.flat()) {
89
+ const name = repository.nameWithOwner;
90
+ if (!includeOwnRepos && name.startsWith(`${canonicalUsername}/`)) {
91
+ continue;
92
+ }
93
+ const existing = repos.get(name);
94
+ if (existing) {
95
+ existing.contributions += contributions.totalCount;
96
+ existing.years.add(year);
97
+ }
98
+ else {
99
+ repos.set(name, {
100
+ contributions: contributions.totalCount,
101
+ years: new Set([year]),
102
+ });
103
+ }
104
+ }
105
+ }
106
+ }
107
+ // each saturated range pushes both of its halves
108
+ const saturatedCount = nextPending.length / 2;
109
+ if (saturatedCount > 0) {
110
+ logger.log(`found ${saturatedCount} saturated ranges, splitting and retrying...`);
111
+ }
112
+ pending = nextPending;
113
+ }
114
+ return repos;
115
+ };
116
+ /**
117
+ * Whether a repository was excluded by the query or by the deployment.
118
+ *
119
+ * An entry matches either the full `owner/name` or the bare `name`,
120
+ * so a host's `EXCLUDE_REPO` — which the other cards match against bare names — still bites here,
121
+ * where a repository is identified by its owner as well.
122
+ *
123
+ * @returns The test, over lowercased entries.
124
+ */
125
+ const excludes = (entries) => {
126
+ const excluded = new Set(entries.map((entry) => entry.toLowerCase()));
127
+ return (nameWithOwner) => {
128
+ const lowered = nameWithOwner.toLowerCase();
129
+ return excluded.has(lowered) || excluded.has(lowered.slice(lowered.indexOf('/') + 1));
130
+ };
131
+ };
132
+ /**
133
+ * Rank the repositories a user has contributed to, most contributions first.
134
+ *
135
+ * `from` and `to` narrow the walk to the range they name,
136
+ * so the ranking, the totals and the year marks all cover that range and nothing else.
137
+ * A range the account contributed in no year of draws an empty card rather than an error.
138
+ *
139
+ * Ties keep the walk's order, which is chronological:
140
+ * the repository first seen in the earliest range comes first.
141
+ *
142
+ * An excluded repository is dropped before the total is counted,
143
+ * so the footer's "top n of m" never counts one the card was told not to show.
144
+ *
145
+ * @returns The ranked slice, and the totals it was taken from.
146
+ */
147
+ const fetchContributedTo = async ({ username, include_own_repos = false, repos_count, exclude_repo = [], from, to, }, config) => {
148
+ if (!username) {
149
+ throw CardError.missingParam(['username']);
150
+ }
151
+ const count = repos_count !== undefined && Number.isFinite(repos_count)
152
+ ? clampValue(repos_count, 1, MAX_REPOS_COUNT)
153
+ : DEFAULT_REPOS_COUNT;
154
+ const res = await retryer(yearsFetcher, { login: username }, config);
155
+ if (res.data.errors) {
156
+ throw graphqlError(res.data.errors, res.statusText, CONTRIBUTION_YEARS_ERROR);
157
+ }
158
+ const { user } = res.data.data;
159
+ if (!user) {
160
+ throw new CardError('Could not fetch user.', {
161
+ code: 'not_found',
162
+ secondaryMessage: USER_NOT_FOUND,
163
+ });
164
+ }
165
+ const ranges = toContributionRanges(user.contributionsCollection.contributionYears, toRange(from, to));
166
+ const years = ranges.map((range) => range.from.getUTCFullYear());
167
+ const found = await fetchReposContributedTo(user.login, ranges, include_own_repos, config);
168
+ const isExcluded = excludes([...exclude_repo, ...config.excludeRepositories]);
169
+ const kept = [...found].filter(([nameWithOwner]) => !isExcluded(nameWithOwner));
170
+ const ranked = kept
171
+ .map(([nameWithOwner, { contributions, years: repoYears }]) => ({
172
+ nameWithOwner,
173
+ contributions,
174
+ years: [...repoYears].toSorted((a, b) => a - b),
175
+ }))
176
+ .toSorted((a, b) => b.contributions - a.contributions);
177
+ return {
178
+ login: user.login,
179
+ repos: ranked.slice(0, count),
180
+ totalRepos: kept.length,
181
+ years,
182
+ };
183
+ };
184
+ export { fetchContributedTo, fetchReposContributedTo };
@@ -0,0 +1,13 @@
1
+ import { CardError } from '../common/error.ts';
2
+ import type { GraphQLResponse } from '../common/http.ts';
3
+ /**
4
+ * Turn the GraphQL `errors` a user query answered with into the error to throw.
5
+ *
6
+ * Its own module because the fetchers classify with it, and two of them may not import each other:
7
+ * `stats.ts` reads the repository walk out of `contributed-to.ts`.
8
+ *
9
+ * @returns `not_found` when the user does not resolve; otherwise `upstream`, worded from the first entry or from `fallback`.
10
+ */
11
+ declare const graphqlError: (errors: NonNullable<GraphQLResponse<unknown>['data']['errors']>, statusText: string, fallback: string) => CardError;
12
+ export { graphqlError };
13
+ //# sourceMappingURL=graphql-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graphql-error.d.ts","sourceRoot":"","sources":["../../src/fetchers/graphql-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAkB,MAAM,oBAAoB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAIzD;;;;;;;GAOG;AACH,QAAA,MAAM,YAAY,WACR,WAAW,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,cACnD,MAAM,YACR,MAAM,KACf,SAgBF,CAAC;AAEF,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { CardError, USER_NOT_FOUND } from '../common/error.js';
2
+ import { logger } from '../common/log.js';
3
+ import { wrapTextMultiline } from '../common/render.js';
4
+ /**
5
+ * Turn the GraphQL `errors` a user query answered with into the error to throw.
6
+ *
7
+ * Its own module because the fetchers classify with it, and two of them may not import each other:
8
+ * `stats.ts` reads the repository walk out of `contributed-to.ts`.
9
+ *
10
+ * @returns `not_found` when the user does not resolve; otherwise `upstream`, worded from the first entry or from `fallback`.
11
+ */
12
+ const graphqlError = (errors, statusText, fallback) => {
13
+ logger.error(errors);
14
+ const [firstError] = errors;
15
+ if (firstError?.type === 'NOT_FOUND') {
16
+ return new CardError(firstError.message || 'Could not fetch user.', {
17
+ code: 'not_found',
18
+ secondaryMessage: USER_NOT_FOUND,
19
+ });
20
+ }
21
+ const message = firstError?.message;
22
+ return message
23
+ ? new CardError(wrapTextMultiline(message, 525, 12)[0] ?? '', {
24
+ code: 'upstream',
25
+ secondaryMessage: statusText,
26
+ })
27
+ : new CardError(fallback, { code: 'upstream' });
28
+ };
29
+ export { graphqlError };
@@ -1,9 +1,10 @@
1
+ export { fetchContributedTo } from './contributed-to.ts';
1
2
  export { fetchGist } from './gist.ts';
2
3
  export { fetchRepo } from './repo.ts';
3
4
  export { fetchRepoUserStats, fetchStats } from './stats.ts';
4
5
  export { fetchTopLanguages } from './top-languages.ts';
5
6
  export { fetchWakatimeStats } from './wakatime.ts';
6
- export type { GistData, Lang, RepoUserStats, RepositoryData, StatsData, TopLangData, WakaTimeData, WakaTimeLang, } from './types.ts';
7
+ export type { ContributedRepo, ContributedToData, GistData, Lang, RepoUserStats, RepositoryData, StatsData, TopLangData, WakaTimeData, WakaTimeLang, } from './types.ts';
7
8
  export { CardConfig } from '../common/config.ts';
8
9
  export type { CardConfigInit, PersonalAccessToken } from '../common/config.ts';
9
10
  export type { FetchLike } from '../common/http.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fetchers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEnD,YAAY,EACV,QAAQ,EACR,IAAI,EACJ,aAAa,EACb,cAAc,EACd,SAAS,EACT,WAAW,EACX,YAAY,EACZ,YAAY,GACb,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,YAAY,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fetchers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEnD,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,QAAQ,EACR,IAAI,EACJ,aAAa,EACb,cAAc,EACd,SAAS,EACT,WAAW,EACX,YAAY,EACZ,YAAY,GACb,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,YAAY,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC"}
@@ -1,3 +1,4 @@
1
+ export { fetchContributedTo } from './contributed-to.js';
1
2
  export { fetchGist } from './gist.js';
2
3
  export { fetchRepo } from './repo.js';
3
4
  export { fetchRepoUserStats, fetchStats } from './stats.js';
@@ -1,33 +1,39 @@
1
1
  import type { CardConfig } from '../common/config.ts';
2
2
  import type { RepoUserStats, StatsData } from './types.ts';
3
+ /** The flags that ask for a per-repository count, as the query names them. */
4
+ interface RepoUserStatsFlags {
5
+ include_prs_authored?: boolean | undefined;
6
+ include_prs_commented?: boolean | undefined;
7
+ include_prs_reviewed?: boolean | undefined;
8
+ include_issues_authored?: boolean | undefined;
9
+ include_issues_commented?: boolean | undefined;
10
+ }
3
11
  /**
4
12
  * Fetch the per-repository counts the REST search API answers, one request each.
5
13
  *
6
14
  * @returns Only the counts that were asked for.
7
15
  */
8
- declare const fetchRepoUserStats: ({ username, repo, owner, include_prs_authored, include_prs_commented, include_prs_reviewed, include_issues_authored, include_issues_commented, }: {
16
+ declare const fetchRepoUserStats: ({ username, repo, owner, ...flags }: RepoUserStatsFlags & {
9
17
  username: string;
10
18
  repo?: Array<string>;
11
19
  owner?: Array<string>;
12
- include_prs_authored?: boolean | undefined;
13
- include_prs_commented?: boolean | undefined;
14
- include_prs_reviewed?: boolean | undefined;
15
- include_issues_authored?: boolean | undefined;
16
- include_issues_commented?: boolean | undefined;
17
20
  }, config: CardConfig) => Promise<RepoUserStats>;
18
21
  /**
19
22
  * Fetch stats for a given username.
20
23
  *
21
24
  * @returns Stats data.
22
25
  */
23
- declare const fetchStats: ({ username, include_all_commits, exclude_repo, include_merged_pull_requests, include_discussions, include_discussions_answers, commits_year, repo, owner, include_prs_authored, include_prs_commented, include_prs_reviewed, include_issues_authored, include_issues_commented, ownerAffiliations, include_contributions, include_all_time_contribs, contribs_include_own_repos, }: {
26
+ declare const fetchStats: ({ username, include_all_commits, exclude_repo, include_merged_pull_requests, include_discussions, include_discussions_answers, from, to, repo, owner, include_prs_authored, include_prs_commented, include_prs_reviewed, include_issues_authored, include_issues_commented, ownerAffiliations, include_contributions, include_all_time_contribs, contribs_include_own_repos, }: {
24
27
  username: string | undefined;
25
28
  include_all_commits?: boolean | undefined;
26
29
  exclude_repo?: Array<string>;
27
30
  include_merged_pull_requests?: boolean;
28
31
  include_discussions?: boolean;
29
32
  include_discussions_answers?: boolean;
30
- commits_year?: number | undefined;
33
+ /** Start of the range the commit count covers; GitHub's own last year when neither end is given. */
34
+ from?: Date | undefined;
35
+ /** End of that range. */
36
+ to?: Date | undefined;
31
37
  repo?: Array<string>;
32
38
  owner?: Array<string>;
33
39
  include_prs_authored?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../src/fetchers/stats.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAwBtD,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AA4L3D;;;;GAIG;AACH,QAAA,MAAM,kBAAkB,qJAUnB;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtB,oBAAoB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3C,qBAAqB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5C,oBAAoB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3C,uBAAuB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9C,wBAAwB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAChD,UACO,UAAU,KACjB,OAAO,CAAC,aAAa,CA+DvB,CAAC;AA8KF;;;;GAIG;AACH,QAAA,MAAM,UAAU,uXAoBX;IACD,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,mBAAmB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1C,YAAY,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,iBAAiB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAClC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,0BAA0B,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAClD,UACO,UAAU,KACjB,OAAO,CAAC,SAAS,CA+JnB,CAAC;AAEF,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,CAAC"}
1
+ {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../src/fetchers/stats.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AA4BtD,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAoL3D,8EAA8E;AAC9E,UAAU,kBAAkB;IAC1B,oBAAoB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3C,qBAAqB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5C,oBAAoB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3C,uBAAuB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9C,wBAAwB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAChD;AAyBD;;;;GAIG;AACH,QAAA,MAAM,kBAAkB,wCAMnB,kBAAkB,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACvB,UACO,UAAU,KACjB,OAAO,CAAC,aAAa,CAWvB,CAAC;AA6CF;;;;GAIG;AACH,QAAA,MAAM,UAAU,mXAqBX;IACD,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,mBAAmB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1C,YAAY,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,oGAAoG;IACpG,IAAI,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IACxB,yBAAyB;IACzB,EAAE,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IACtB,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,iBAAiB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAClC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,0BAA0B,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAClD,UACO,UAAU,KACjB,OAAO,CAAC,SAAS,CAsKnB,CAAC;AAEF,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,CAAC"}