@stats-forge/github-stats-forge-core 0.0.2 → 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 (249) hide show
  1. package/README.md +0 -10
  2. package/build/api/api-result.d.ts +6 -7
  3. package/build/api/api-result.d.ts.map +1 -1
  4. package/build/api/api-result.js +4 -5
  5. package/build/api/contributed-to.d.ts +23 -0
  6. package/build/api/contributed-to.d.ts.map +1 -0
  7. package/build/api/contributed-to.js +43 -0
  8. package/build/api/gist.d.ts +8 -22
  9. package/build/api/gist.d.ts.map +1 -1
  10. package/build/api/gist.js +14 -36
  11. package/build/api/handler.d.ts +16 -0
  12. package/build/api/handler.d.ts.map +1 -0
  13. package/build/api/handler.js +29 -0
  14. package/build/api/index.d.ts +13 -12
  15. package/build/api/index.d.ts.map +1 -1
  16. package/build/api/index.js +1 -0
  17. package/build/api/params.d.ts +21 -12
  18. package/build/api/params.d.ts.map +1 -1
  19. package/build/api/params.js +41 -19
  20. package/build/api/pin.d.ts +11 -31
  21. package/build/api/pin.d.ts.map +1 -1
  22. package/build/api/pin.js +35 -63
  23. package/build/api/stats.d.ts +15 -16
  24. package/build/api/stats.d.ts.map +1 -1
  25. package/build/api/stats.js +60 -99
  26. package/build/api/top-langs.d.ts +13 -18
  27. package/build/api/top-langs.d.ts.map +1 -1
  28. package/build/api/top-langs.js +35 -72
  29. package/build/api/wakatime.d.ts +11 -17
  30. package/build/api/wakatime.d.ts.map +1 -1
  31. package/build/api/wakatime.js +27 -64
  32. package/build/calculateRank.d.ts +0 -9
  33. package/build/calculateRank.d.ts.map +1 -1
  34. package/build/calculateRank.js +12 -17
  35. package/build/cards/contributed-to.d.ts +19 -0
  36. package/build/cards/contributed-to.d.ts.map +1 -0
  37. package/build/cards/contributed-to.js +242 -0
  38. package/build/cards/gist.d.ts +3 -4
  39. package/build/cards/gist.d.ts.map +1 -1
  40. package/build/cards/gist.js +38 -43
  41. package/build/cards/options.d.ts +1 -1
  42. package/build/cards/repo.d.ts +14 -5
  43. package/build/cards/repo.d.ts.map +1 -1
  44. package/build/cards/repo.js +93 -103
  45. package/build/cards/stats.d.ts +18 -11
  46. package/build/cards/stats.d.ts.map +1 -1
  47. package/build/cards/stats.js +191 -222
  48. package/build/cards/top-languages.d.ts +18 -33
  49. package/build/cards/top-languages.d.ts.map +1 -1
  50. package/build/cards/top-languages.js +196 -387
  51. package/build/cards/wakatime.d.ts +17 -8
  52. package/build/cards/wakatime.d.ts.map +1 -1
  53. package/build/cards/wakatime.js +133 -187
  54. package/build/common/Card.d.ts +20 -59
  55. package/build/common/Card.d.ts.map +1 -1
  56. package/build/common/Card.js +131 -198
  57. package/build/common/I18n.d.ts +1 -7
  58. package/build/common/I18n.d.ts.map +1 -1
  59. package/build/common/I18n.js +7 -8
  60. package/build/common/brand.d.ts +76 -0
  61. package/build/common/brand.d.ts.map +1 -0
  62. package/build/common/brand.js +73 -0
  63. package/build/common/color.d.ts +4 -17
  64. package/build/common/color.d.ts.map +1 -1
  65. package/build/common/color.js +67 -49
  66. package/build/common/config.d.ts +4 -14
  67. package/build/common/config.d.ts.map +1 -1
  68. package/build/common/config.js +3 -16
  69. package/build/common/constants.d.ts +6 -2
  70. package/build/common/constants.d.ts.map +1 -1
  71. package/build/common/constants.js +5 -1
  72. package/build/common/date.d.ts +50 -5
  73. package/build/common/date.d.ts.map +1 -1
  74. package/build/common/date.js +104 -4
  75. package/build/common/emojiMap.d.ts +8 -0
  76. package/build/common/emojiMap.d.ts.map +1 -0
  77. package/build/common/emojiMap.js +10 -0
  78. package/build/common/emojiMap.json +1915 -0
  79. package/build/common/error.d.ts +1 -10
  80. package/build/common/error.d.ts.map +1 -1
  81. package/build/common/error.js +1 -10
  82. package/build/common/fmt.d.ts +3 -15
  83. package/build/common/fmt.d.ts.map +1 -1
  84. package/build/common/fmt.js +7 -35
  85. package/build/common/http.d.ts +2 -7
  86. package/build/common/http.d.ts.map +1 -1
  87. package/build/common/http.js +8 -17
  88. package/build/common/icons.d.ts +20 -20
  89. package/build/common/icons.d.ts.map +1 -1
  90. package/build/common/icons.js +108 -67
  91. package/build/common/languageColors.d.ts +0 -1
  92. package/build/common/languageColors.d.ts.map +1 -1
  93. package/build/common/languageColors.js +1 -4
  94. package/build/common/languageColors.json +18 -2
  95. package/build/common/ops.d.ts +11 -17
  96. package/build/common/ops.d.ts.map +1 -1
  97. package/build/common/ops.js +22 -39
  98. package/build/common/render.d.ts +34 -82
  99. package/build/common/render.d.ts.map +1 -1
  100. package/build/common/render.js +225 -259
  101. package/build/common/retryer.d.ts +2 -7
  102. package/build/common/retryer.d.ts.map +1 -1
  103. package/build/common/retryer.js +4 -11
  104. package/build/fetchers/contributed-to.d.ts +62 -0
  105. package/build/fetchers/contributed-to.d.ts.map +1 -0
  106. package/build/fetchers/contributed-to.js +184 -0
  107. package/build/fetchers/gist.d.ts +2 -5
  108. package/build/fetchers/gist.d.ts.map +1 -1
  109. package/build/fetchers/gist.js +1 -5
  110. package/build/fetchers/graphql-error.d.ts +13 -0
  111. package/build/fetchers/graphql-error.d.ts.map +1 -0
  112. package/build/fetchers/graphql-error.js +29 -0
  113. package/build/fetchers/index.d.ts +13 -12
  114. package/build/fetchers/index.d.ts.map +1 -1
  115. package/build/fetchers/index.js +1 -0
  116. package/build/fetchers/repo.d.ts +2 -11
  117. package/build/fetchers/repo.d.ts.map +1 -1
  118. package/build/fetchers/repo.js +20 -33
  119. package/build/fetchers/stats.d.ts +16 -40
  120. package/build/fetchers/stats.d.ts.map +1 -1
  121. package/build/fetchers/stats.js +89 -271
  122. package/build/fetchers/top-languages.d.ts +2 -9
  123. package/build/fetchers/top-languages.d.ts.map +1 -1
  124. package/build/fetchers/top-languages.js +16 -47
  125. package/build/fetchers/types.d.ts +25 -1
  126. package/build/fetchers/types.d.ts.map +1 -1
  127. package/build/fetchers/wakatime.d.ts +2 -6
  128. package/build/fetchers/wakatime.d.ts.map +1 -1
  129. package/build/fetchers/wakatime.js +1 -5
  130. package/build/graphql/contributionsCollection.d.ts +9 -0
  131. package/build/graphql/contributionsCollection.d.ts.map +1 -0
  132. package/build/graphql/contributionsCollection.js +12 -0
  133. package/build/graphql/contributionsDocument.d.ts +6 -6
  134. package/build/graphql/contributionsDocument.d.ts.map +1 -1
  135. package/build/graphql/contributionsDocument.js +8 -16
  136. package/build/graphql/generated/contributed-to.d.ts +22 -0
  137. package/build/graphql/generated/contributed-to.d.ts.map +1 -0
  138. package/build/graphql/generated/contributed-to.js +11 -0
  139. package/build/graphql/generated/gist.d.ts +1 -1
  140. package/build/graphql/generated/repo.d.ts +1 -1
  141. package/build/graphql/generated/stats.d.ts +16 -4
  142. package/build/graphql/generated/stats.d.ts.map +1 -1
  143. package/build/graphql/generated/stats.js +2 -2
  144. package/build/graphql/generated/top-languages.d.ts +2 -2
  145. package/build/graphql/graphqlDocument.d.ts +1 -2
  146. package/build/graphql/graphqlDocument.d.ts.map +1 -1
  147. package/build/graphql/graphqlDocument.js +0 -1
  148. package/build/graphql/reposContributedToDocument.d.ts +4 -5
  149. package/build/graphql/reposContributedToDocument.d.ts.map +1 -1
  150. package/build/graphql/reposContributedToDocument.js +11 -7
  151. package/build/markup/css.d.ts +35 -0
  152. package/build/markup/css.d.ts.map +1 -0
  153. package/build/markup/css.js +64 -0
  154. package/build/markup/element.d.ts +41 -0
  155. package/build/markup/element.d.ts.map +1 -0
  156. package/build/markup/element.js +127 -0
  157. package/build/markup/html.d.ts +13 -0
  158. package/build/markup/html.d.ts.map +1 -0
  159. package/build/markup/html.js +27 -0
  160. package/build/markup/index.d.ts +9 -0
  161. package/build/markup/index.d.ts.map +1 -0
  162. package/build/markup/index.js +6 -0
  163. package/build/markup/serialize.d.ts +16 -0
  164. package/build/markup/serialize.d.ts.map +1 -0
  165. package/build/markup/serialize.js +17 -0
  166. package/build/themes/index.d.ts +332 -333
  167. package/build/themes/index.d.ts.map +1 -1
  168. package/build/themes/index.js +1 -1
  169. package/build/translations.d.ts +66 -7
  170. package/build/translations.d.ts.map +1 -1
  171. package/build/translations.js +686 -629
  172. package/package.json +17 -26
  173. package/src/api/api-result.ts +10 -9
  174. package/src/api/contributed-to.ts +84 -0
  175. package/src/api/gist.ts +22 -46
  176. package/src/api/handler.ts +42 -0
  177. package/src/api/index.ts +13 -12
  178. package/src/api/params.ts +74 -29
  179. package/src/api/pin.ts +38 -65
  180. package/src/api/stats.ts +81 -118
  181. package/src/api/top-langs.ts +38 -80
  182. package/src/api/wakatime.ts +35 -73
  183. package/src/calculateRank.ts +12 -23
  184. package/src/cards/contributed-to.ts +373 -0
  185. package/src/cards/gist.ts +55 -49
  186. package/src/cards/options.ts +1 -1
  187. package/src/cards/repo.ts +134 -120
  188. package/src/cards/stats.ts +262 -252
  189. package/src/cards/top-languages.ts +315 -422
  190. package/src/cards/wakatime.ts +207 -225
  191. package/src/common/Card.ts +207 -214
  192. package/src/common/I18n.ts +7 -8
  193. package/src/common/brand.ts +109 -0
  194. package/src/common/color.ts +80 -57
  195. package/src/common/config.ts +5 -18
  196. package/src/common/constants.ts +8 -2
  197. package/src/common/date.ts +135 -5
  198. package/src/common/emojiMap.json +1915 -0
  199. package/src/common/emojiMap.ts +13 -0
  200. package/src/common/error.ts +2 -11
  201. package/src/common/fmt.ts +7 -44
  202. package/src/common/http.ts +9 -15
  203. package/src/common/icons.ts +122 -42
  204. package/src/common/languageColors.json +18 -2
  205. package/src/common/languageColors.ts +1 -4
  206. package/src/common/ops.ts +31 -48
  207. package/src/common/render.ts +334 -284
  208. package/src/common/retryer.ts +8 -15
  209. package/src/fetchers/contributed-to.ts +271 -0
  210. package/src/fetchers/gist.ts +8 -12
  211. package/src/fetchers/graphql-error.ts +36 -0
  212. package/src/fetchers/index.ts +15 -12
  213. package/src/fetchers/repo.ts +27 -40
  214. package/src/fetchers/stats.ts +157 -366
  215. package/src/fetchers/top-languages.ts +27 -55
  216. package/src/fetchers/types.ts +27 -1
  217. package/src/fetchers/wakatime.ts +5 -9
  218. package/src/graphql/contributionsCollection.ts +18 -0
  219. package/src/graphql/contributionsDocument.ts +14 -19
  220. package/src/graphql/generated/contributed-to.ts +25 -0
  221. package/src/graphql/generated/gist.ts +1 -1
  222. package/src/graphql/generated/repo.ts +1 -1
  223. package/src/graphql/generated/stats.ts +21 -8
  224. package/src/graphql/generated/top-languages.ts +2 -2
  225. package/src/graphql/graphqlDocument.ts +1 -2
  226. package/src/graphql/queries/contributed-to.graphql +8 -0
  227. package/src/graphql/queries/stats.graphql +14 -2
  228. package/src/graphql/reposContributedToDocument.ts +16 -15
  229. package/src/markup/css.ts +118 -0
  230. package/src/markup/element.ts +246 -0
  231. package/src/markup/html.ts +34 -0
  232. package/src/markup/index.ts +9 -0
  233. package/src/markup/serialize.ts +27 -0
  234. package/src/themes/index.ts +1 -1
  235. package/src/translations.ts +699 -629
  236. package/build/cards/index.d.ts +0 -11
  237. package/build/cards/index.d.ts.map +0 -1
  238. package/build/cards/index.js +0 -7
  239. package/build/common/html.d.ts +0 -8
  240. package/build/common/html.d.ts.map +0 -1
  241. package/build/common/html.js +0 -14
  242. package/build/index.d.ts +0 -15
  243. package/build/index.d.ts.map +0 -1
  244. package/build/index.js +0 -10
  245. package/src/_emoji-name-map.d.ts +0 -10
  246. package/src/_github-username-regex.d.ts +0 -4
  247. package/src/cards/index.ts +0 -21
  248. package/src/common/html.ts +0 -17
  249. package/src/index.ts +0 -18
@@ -1,30 +1,22 @@
1
- import type { CardConfig } from '../common/config.js';
2
- import { CardError, USER_NOT_FOUND } from '../common/error.js';
3
- import { wrapTextMultiline } from '../common/fmt.js';
4
- import { createGraphQLFetcher } from '../common/http.js';
5
- import { logger } from '../common/log.js';
6
- import { parseOwnerAffiliations } from '../common/ops.js';
7
- import { retryer } from '../common/retryer.js';
8
- import { TopLanguagesDocument } from '../graphql/generated/top-languages.js';
1
+ import type { CardConfig } from '../common/config.ts';
2
+ import { CardError } from '../common/error.ts';
3
+ import { createGraphQLFetcher } from '../common/http.ts';
4
+ import { parseOwnerAffiliations } from '../common/ops.ts';
5
+ import { retryer } from '../common/retryer.ts';
6
+ import { TopLanguagesDocument } from '../graphql/generated/top-languages.ts';
9
7
  import type {
10
8
  TopLanguageFragment,
11
9
  TopLanguagesRepositoryFragment,
12
- } from '../graphql/generated/top-languages.js';
10
+ } from '../graphql/generated/top-languages.ts';
13
11
 
14
- import type { Lang, TopLangData } from './types.js';
12
+ import { graphqlError } from './graphql-error.ts';
13
+ import type { Lang, TopLangData } from './types.ts';
15
14
 
16
15
  const fetcher = createGraphQLFetcher(TopLanguagesDocument, 'token');
17
16
 
18
17
  /**
19
18
  * Fetch top languages for a given username.
20
19
  *
21
- * @param props Fetcher props.
22
- * @param props.username GitHub username.
23
- * @param props.exclude_repo List of repositories to exclude. Default: [].
24
- * @param props.size_weight Weightage to be given to size.
25
- * @param props.count_weight Weightage to be given to count.
26
- * @param props.ownerAffiliations The owner affiliations to filter by. Default: OWNER.
27
- * @param config Deployment config supplying the PAT pool.
28
20
  * @returns Top languages data.
29
21
  */
30
22
  const fetchTopLanguages = async (
@@ -58,23 +50,10 @@ const fetchTopLanguages = async (
58
50
  );
59
51
 
60
52
  if (res.data.errors) {
61
- logger.error(res.data.errors);
62
- const firstError = res.data.errors[0];
63
- if (firstError?.type === 'NOT_FOUND') {
64
- throw new CardError(firstError.message || 'Could not fetch user.', {
65
- code: 'not_found',
66
- secondaryMessage: USER_NOT_FOUND,
67
- });
68
- }
69
- if (firstError?.message) {
70
- throw new CardError(wrapTextMultiline(firstError.message, 525, 12)[0] ?? '', {
71
- code: 'upstream',
72
- secondaryMessage: res.statusText,
73
- });
74
- }
75
- throw new CardError(
53
+ throw graphqlError(
54
+ res.data.errors,
55
+ res.statusText,
76
56
  'Something went wrong while trying to retrieve the language data using the GraphQL API.',
77
- { code: 'upstream' },
78
57
  );
79
58
  }
80
59
 
@@ -82,53 +61,46 @@ const fetchTopLanguages = async (
82
61
  const allExcludedRepos = [...exclude_repo, ...config.excludeRepositories];
83
62
 
84
63
  // populate repoToHide map for quick lookup while filtering out
85
- allExcludedRepos.forEach((repoName) => {
64
+ for (const repoName of allExcludedRepos) {
86
65
  repoToHide[repoName] = true;
87
- });
66
+ }
88
67
 
89
68
  // filter out repositories to be hidden
90
69
  const repoNodes = (res.data.data.user?.repositories.nodes ?? []).filter(
91
70
  (node): node is TopLanguagesRepositoryFragment => !!node && !repoToHide[node.name],
92
71
  );
93
72
 
94
- // flatten edges across repos. Order matters: `concat(acc)` prepends, and the
95
- // shared repoCount below depends on visitation order.
96
- const languageEdges = repoNodes.reduce<Array<TopLanguageFragment>>((acc, repo) => {
73
+ let languageEdges: Array<TopLanguageFragment> = [];
74
+ for (const repo of repoNodes) {
97
75
  const edges = (repo.languages?.edges ?? []).filter(
98
76
  (edge): edge is TopLanguageFragment => !!edge,
99
77
  );
100
- return edges.length > 0 ? edges.concat(acc) : acc;
101
- }, []);
78
+ if (edges.length > 0) {
79
+ languageEdges = [...edges, ...languageEdges];
80
+ }
81
+ }
102
82
 
103
- // accumulate size and repo count per language
83
+ // accumulate size and repo count per language, each read back off the language's own entry
104
84
  const languageMap: Record<string, Lang> = {};
105
- let repoCount = 0;
106
85
  for (const edge of languageEdges) {
107
86
  const existing = languageMap[edge.node.name];
108
-
109
- // same language seen again: add to its size and bump count; else reset to 1
110
- let langSize = edge.size;
111
- if (existing && edge.node.name === existing.name) {
112
- langSize = edge.size + existing.size;
113
- repoCount += 1;
114
- } else {
115
- repoCount = 1;
116
- }
117
87
  languageMap[edge.node.name] = {
118
88
  name: edge.node.name,
119
89
  color: edge.node.color,
120
- size: langSize,
121
- count: repoCount,
90
+ size: edge.size + (existing?.size ?? 0),
91
+ count: (existing?.count ?? 0) + 1,
122
92
  };
123
93
  }
124
94
 
125
95
  // comparison index calculation
126
96
  for (const lang of Object.values(languageMap)) {
127
- lang.size = Math.pow(lang.size, size_weight) * Math.pow(lang.count, count_weight);
97
+ lang.size = lang.size ** size_weight * lang.count ** count_weight;
128
98
  }
129
99
 
130
100
  // return languages sorted by (weighted) size, descending
131
- return Object.fromEntries(Object.entries(languageMap).sort(([, a], [, b]) => b.size - a.size));
101
+ return Object.fromEntries(
102
+ Object.entries(languageMap).toSorted(([, a], [, b]) => b.size - a.size),
103
+ );
132
104
  };
133
105
 
134
106
  export { fetchTopLanguages };
@@ -1,4 +1,5 @@
1
- import type { RepoInfoFragment } from '../graphql/generated/repo.js';
1
+ import type { GitHubDateRange } from '../common/date.ts';
2
+ import type { RepoInfoFragment } from '../graphql/generated/repo.ts';
2
3
 
3
4
  export interface GistData {
4
5
  name: string;
@@ -39,6 +40,8 @@ export interface StatsData {
39
40
  mergedPRsPercentage: number;
40
41
  totalReviews: number;
41
42
  totalCommits: number;
43
+ /** The range `totalCommits` covers; `undefined` when it is GitHub's own last year, or all time. */
44
+ commitsRange: GitHubDateRange | undefined;
42
45
  totalIssues: number;
43
46
  totalStars: number;
44
47
  totalDiscussionsStarted: number;
@@ -54,6 +57,29 @@ export interface StatsData {
54
57
  rank: { level: string; percentile: number };
55
58
  }
56
59
 
60
+ /** One repository a user contributed to, as the card ranks it. */
61
+ export interface ContributedRepo {
62
+ nameWithOwner: string;
63
+ /**
64
+ * Commit-days plus issues and pull requests opened, summed over every year.
65
+ * Not a commit count: GitHub groups commit contributions by day, not by commit.
66
+ */
67
+ contributions: number;
68
+ /** Every year the user contributed to it, ascending. */
69
+ years: Array<number>;
70
+ }
71
+
72
+ export interface ContributedToData {
73
+ /** GitHub's own casing of the login, which the query param need not match. */
74
+ login: string;
75
+ /** The ranked slice, most contributions first. */
76
+ repos: Array<ContributedRepo>;
77
+ /** Every repository the walk found, which `repos` is a slice of. */
78
+ totalRepos: number;
79
+ /** The account's contribution years, ascending — the range the year marks cover. */
80
+ years: Array<number>;
81
+ }
82
+
57
83
  export interface Lang {
58
84
  name: string;
59
85
  // GitHub's GraphQL `Language.color` is nullable — the card falls back to a default.
@@ -1,18 +1,14 @@
1
- import type { CardConfig } from '../common/config.js';
2
- import { CardError, WAKATIME_USER_NOT_FOUND } from '../common/error.js';
3
- import { httpRequest } from '../common/http.js';
1
+ import type { CardConfig } from '../common/config.ts';
2
+ import { CardError, WAKATIME_USER_NOT_FOUND } from '../common/error.ts';
3
+ import { httpRequest } from '../common/http.ts';
4
4
 
5
- import type { WakaTimeData } from './types.js';
5
+ import type { WakaTimeData } from './types.ts';
6
6
 
7
7
  /**
8
8
  * WakaTime data fetcher.
9
9
  *
10
10
  * WakaTime needs no GitHub token; the config is here for the transport it carries.
11
11
  *
12
- * @param props Fetcher props.
13
- * @param props.username WakaTime username.
14
- * @param props.api_domain Optional WakaTime API domain (defaults to `wakatime.com`).
15
- * @param config Deployment config supplying the transport.
16
12
  * @returns WakaTime data response.
17
13
  */
18
14
  const fetchWakatimeStats = async (
@@ -29,7 +25,7 @@ const fetchWakatimeStats = async (
29
25
  throw CardError.missingParam(['username']);
30
26
  }
31
27
 
32
- const domain = api_domain ? api_domain.replace(/\/$/gi, '') : 'wakatime.com';
28
+ const domain = api_domain ? api_domain.replaceAll(/\/$/gi, '') : 'wakatime.com';
33
29
  const res = await httpRequest<{ data: WakaTimeData }>(
34
30
  config.fetch,
35
31
  `https://${domain}/api/v1/users/${username}/stats?is_including_today=true`,
@@ -0,0 +1,18 @@
1
+ import type { GitHubDateRange } from '../common/date.ts';
2
+ import { toGitHubDateTime } from '../common/date.ts';
3
+
4
+ /** Naming `to` is not optional: GitHub defaults an omitted one to a year after `from`. */
5
+ const contributionsCollectionOf = ({ from, to }: GitHubDateRange): string =>
6
+ `contributionsCollection(from: "${toGitHubDateTime(from)}", to: "${toGitHubDateTime(to)}")`;
7
+
8
+ /**
9
+ * One `contributionsCollection` field per range, aliased by position.
10
+ *
11
+ * @returns The fields, for a query that selects `fragment`.
12
+ */
13
+ const aliasedRanges = (ranges: Array<GitHubDateRange>, fragment: string): string =>
14
+ ranges
15
+ .map((range, index) => `range_${index}: ${contributionsCollectionOf(range)} { ...${fragment} }`)
16
+ .join('\n');
17
+
18
+ export { aliasedRanges };
@@ -1,44 +1,39 @@
1
- import { getGitHubYearRange, toGitHubDateTime } from '../common/date.js';
1
+ import type { GitHubDateRange } from '../common/date.ts';
2
2
 
3
- import type { YearContributionsFragment } from './generated/stats.js';
4
- import { graphqlDocument } from './graphqlDocument.js';
3
+ import { aliasedRanges } from './contributionsCollection.ts';
4
+ import type { RangeContributionsFragment } from './generated/stats.ts';
5
+ import type { GraphQLDocument } from './graphqlDocument.ts';
6
+ import { graphqlDocument } from './graphqlDocument.ts';
5
7
 
6
8
  interface ContributionsQueryVariables {
7
9
  login: string;
8
10
  }
9
11
 
10
12
  interface ContributionsQuery {
11
- user: Record<`year_${number}`, YearContributionsFragment> | null;
13
+ user: Record<`range_${number}`, RangeContributionsFragment> | null;
12
14
  }
13
15
 
14
16
  /**
15
- * Build the all-time contributions query, one aliased `contributionsCollection` field per year.
16
- * The shape is only known at runtime.
17
+ * Build the contributions query for a set of ranges. The shape is only known at runtime.
17
18
  *
18
- * @param years Contribution years, one `year_<year>` alias each.
19
19
  * @returns Document for `createGraphQLFetcher`.
20
20
  */
21
- const buildContributionsDocument = (years: Array<number>) => {
22
- const yearFields = years
23
- .map((year) => {
24
- const { from, to } = getGitHubYearRange(year);
25
- return `year_${year}: contributionsCollection(from: "${toGitHubDateTime(from)}", to: "${toGitHubDateTime(to)}") { ...YearContributions }`;
26
- })
27
- .join('\n');
28
-
21
+ const buildContributionsDocument = (
22
+ ranges: Array<GitHubDateRange>,
23
+ ): GraphQLDocument<ContributionsQuery, ContributionsQueryVariables> =>
29
24
  // fragment must match queries/stats.graphql, which generates its type
30
- return graphqlDocument<ContributionsQuery, ContributionsQueryVariables>(`
25
+ graphqlDocument<ContributionsQuery, ContributionsQueryVariables>(`
31
26
  query userContributions($login: String!) {
32
27
  user(login: $login) {
33
- ${yearFields}
28
+ ${aliasedRanges(ranges, 'RangeContributions')}
34
29
  }
35
30
  }
36
- fragment YearContributions on ContributionsCollection {
31
+ fragment RangeContributions on ContributionsCollection {
32
+ totalCommitContributions
37
33
  contributionCalendar {
38
34
  totalContributions
39
35
  }
40
36
  }`);
41
- };
42
37
 
43
38
  export { buildContributionsDocument };
44
39
  export type { ContributionsQuery };
@@ -0,0 +1,25 @@
1
+ // Generated file — see .github/CONTRIBUTING.md
2
+
3
+ /** Internal type. DO NOT USE DIRECTLY. */
4
+ type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
5
+ import { graphqlDocument } from '../graphqlDocument.ts';
6
+ export type UserContributionYearsQueryVariables = Exact<{
7
+ login: string;
8
+ }>;
9
+
10
+ export type UserContributionYearsQuery = {
11
+ user: { login: string; contributionsCollection: { contributionYears: Array<number> } } | null;
12
+ };
13
+
14
+ export const UserContributionYearsDocument = graphqlDocument<
15
+ UserContributionYearsQuery,
16
+ UserContributionYearsQueryVariables
17
+ >(`
18
+ query userContributionYears($login: String!) {
19
+ user(login: $login) {
20
+ login
21
+ contributionsCollection {
22
+ contributionYears
23
+ }
24
+ }
25
+ }`);
@@ -2,7 +2,7 @@
2
2
 
3
3
  /** Internal type. DO NOT USE DIRECTLY. */
4
4
  type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
5
- import { graphqlDocument } from '../graphqlDocument.js';
5
+ import { graphqlDocument } from '../graphqlDocument.ts';
6
6
  export type GistFileInfoFragment = {
7
7
  name: string | null;
8
8
  size: number | null;
@@ -2,7 +2,7 @@
2
2
 
3
3
  /** Internal type. DO NOT USE DIRECTLY. */
4
4
  type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
5
- import { graphqlDocument } from '../graphqlDocument.js';
5
+ import { graphqlDocument } from '../graphqlDocument.ts';
6
6
  export type RepoInfoFragment = {
7
7
  name: string;
8
8
  nameWithOwner: string;
@@ -2,9 +2,9 @@
2
2
 
3
3
  /** Internal type. DO NOT USE DIRECTLY. */
4
4
  type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
5
- import { graphqlDocument } from '../graphqlDocument.js';
5
+ import { graphqlDocument } from '../graphqlDocument.ts';
6
6
 
7
- import type * as Types from './common.js';
7
+ import type * as Types from './common.ts';
8
8
  export type RepoNodeFragment = { name: string; stargazerCount: number };
9
9
 
10
10
  export type RepoStarsFragment = {
@@ -42,6 +42,7 @@ export type UserInfoQueryVariables = Exact<{
42
42
  includeDiscussions: boolean;
43
43
  includeDiscussionsAnswers: boolean;
44
44
  startTime?: string | null | undefined;
45
+ endTime?: string | null | undefined;
45
46
  ownerAffiliations?:
46
47
  | Array<Types.RepositoryAffiliation | null | undefined>
47
48
  | Types.RepositoryAffiliation
@@ -73,12 +74,24 @@ export type UserInfoQuery = {
73
74
  } | null;
74
75
  };
75
76
 
76
- export type YearContributionsFragment = { contributionCalendar: { totalContributions: number } };
77
+ export type RangeContributionsFragment = {
78
+ totalCommitContributions: number;
79
+ contributionCalendar: { totalContributions: number };
80
+ };
77
81
 
78
82
  export type RangeContributionsByRepoFragment = {
79
- commitContributionsByRepository: Array<{ repository: { nameWithOwner: string } }>;
80
- issueContributionsByRepository: Array<{ repository: { nameWithOwner: string } }>;
81
- pullRequestContributionsByRepository: Array<{ repository: { nameWithOwner: string } }>;
83
+ commitContributionsByRepository: Array<{
84
+ repository: { nameWithOwner: string };
85
+ contributions: { totalCount: number };
86
+ }>;
87
+ issueContributionsByRepository: Array<{
88
+ repository: { nameWithOwner: string };
89
+ contributions: { totalCount: number };
90
+ }>;
91
+ pullRequestContributionsByRepository: Array<{
92
+ repository: { nameWithOwner: string };
93
+ contributions: { totalCount: number };
94
+ }>;
82
95
  repositoryContributions?: {
83
96
  nodes: Array<{ repository: { nameWithOwner: string } } | null> | null;
84
97
  };
@@ -113,11 +126,11 @@ fragment RepoNode on Repository {
113
126
  }`);
114
127
 
115
128
  export const UserInfoDocument = graphqlDocument<UserInfoQuery, UserInfoQueryVariables>(`
116
- query userInfo($login: String!, $after: String, $includeMergedPullRequests: Boolean!, $includeDiscussions: Boolean!, $includeDiscussionsAnswers: Boolean!, $startTime: DateTime = null, $ownerAffiliations: [RepositoryAffiliation], $includeUserRepositories: Boolean!) {
129
+ query userInfo($login: String!, $after: String, $includeMergedPullRequests: Boolean!, $includeDiscussions: Boolean!, $includeDiscussionsAnswers: Boolean!, $startTime: DateTime = null, $endTime: DateTime = null, $ownerAffiliations: [RepositoryAffiliation], $includeUserRepositories: Boolean!) {
117
130
  user(login: $login) {
118
131
  name
119
132
  login
120
- commits: contributionsCollection(from: $startTime) {
133
+ commits: contributionsCollection(from: $startTime, to: $endTime) {
121
134
  totalCommitContributions
122
135
  }
123
136
  reviews: contributionsCollection {
@@ -2,9 +2,9 @@
2
2
 
3
3
  /** Internal type. DO NOT USE DIRECTLY. */
4
4
  type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
5
- import { graphqlDocument } from '../graphqlDocument.js';
5
+ import { graphqlDocument } from '../graphqlDocument.ts';
6
6
 
7
- import type * as Types from './common.js';
7
+ import type * as Types from './common.ts';
8
8
  export type TopLanguageFragment = { size: number; node: { color: string | null; name: string } };
9
9
 
10
10
  export type TopLanguagesRepositoryFragment = {
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * `text` is what goes over the wire; the other two members only carry types and are never assigned.
5
5
  * Documents are generated from `queries/*.graphql`
6
- * See `scripts/generate-graphql-types.js`.
6
+ * See `scripts/generate-graphql-types.ts`.
7
7
  */
8
8
  interface GraphQLDocument<TResult, TVariables> {
9
9
  readonly text: string;
@@ -14,7 +14,6 @@ interface GraphQLDocument<TResult, TVariables> {
14
14
  /**
15
15
  * Build a typed document.
16
16
  *
17
- * @param text The query text, including every fragment it spreads.
18
17
  * @returns The document to pass to the function created by `createGraphQLFetcher`.
19
18
  */
20
19
  const graphqlDocument = <TResult, TVariables>(
@@ -0,0 +1,8 @@
1
+ query userContributionYears($login: String!) {
2
+ user(login: $login) {
3
+ login
4
+ contributionsCollection {
5
+ contributionYears
6
+ }
7
+ }
8
+ }
@@ -34,13 +34,14 @@ query userInfo(
34
34
  $includeDiscussions: Boolean!
35
35
  $includeDiscussionsAnswers: Boolean!
36
36
  $startTime: DateTime = null
37
+ $endTime: DateTime = null
37
38
  $ownerAffiliations: [RepositoryAffiliation]
38
39
  $includeUserRepositories: Boolean!
39
40
  ) {
40
41
  user(login: $login) {
41
42
  name
42
43
  login
43
- commits: contributionsCollection(from: $startTime) {
44
+ commits: contributionsCollection(from: $startTime, to: $endTime) {
44
45
  totalCommitContributions
45
46
  }
46
47
  reviews: contributionsCollection {
@@ -81,27 +82,38 @@ query userInfo(
81
82
  }
82
83
  }
83
84
 
84
- fragment YearContributions on ContributionsCollection {
85
+ fragment RangeContributions on ContributionsCollection {
86
+ totalCommitContributions
85
87
  contributionCalendar {
86
88
  totalContributions
87
89
  }
88
90
  }
89
91
 
92
+ # `contributions` takes no slicing argument on purpose: `totalCount` alone costs no nodes.
90
93
  fragment RangeContributionsByRepo on ContributionsCollection {
91
94
  commitContributionsByRepository(maxRepositories: $maxRepositories) {
92
95
  repository {
93
96
  nameWithOwner
94
97
  }
98
+ contributions {
99
+ totalCount
100
+ }
95
101
  }
96
102
  issueContributionsByRepository(maxRepositories: $maxRepositories) {
97
103
  repository {
98
104
  nameWithOwner
99
105
  }
106
+ contributions {
107
+ totalCount
108
+ }
100
109
  }
101
110
  pullRequestContributionsByRepository(maxRepositories: $maxRepositories) {
102
111
  repository {
103
112
  nameWithOwner
104
113
  }
114
+ contributions {
115
+ totalCount
116
+ }
105
117
  }
106
118
  repositoryContributions(first: $maxRepositories) @include(if: $includeOwnRepos) {
107
119
  nodes {
@@ -1,8 +1,9 @@
1
- import type { GitHubDateRange } from '../common/date.js';
2
- import { toGitHubDateTime } from '../common/date.js';
1
+ import type { GitHubDateRange } from '../common/date.ts';
3
2
 
4
- import type { RangeContributionsByRepoFragment } from './generated/stats.js';
5
- import { graphqlDocument } from './graphqlDocument.js';
3
+ import { aliasedRanges } from './contributionsCollection.ts';
4
+ import type { RangeContributionsByRepoFragment } from './generated/stats.ts';
5
+ import type { GraphQLDocument } from './graphqlDocument.ts';
6
+ import { graphqlDocument } from './graphqlDocument.ts';
6
7
 
7
8
  /** max value GitHub allows for `first/maxRepositories` */
8
9
  const MAX_REPOSITORIES_LIMIT = 100;
@@ -24,21 +25,12 @@ interface ReposContributedToQuery {
24
25
  * Mirrors the `contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]`
25
26
  * filter used by `repositoriesContributedTo` in `stats.graphql`.
26
27
  *
27
- * @param ranges Ranges to fetch, one `range_<index>` alias each.
28
- * @param includeOwnRepos Whether to select `repositoryContributions`.
29
28
  * @returns Document for `createGraphQLFetcher`.
30
29
  */
31
30
  const buildReposContributedToDocument = (
32
31
  ranges: Array<GitHubDateRange>,
33
32
  includeOwnRepos: boolean,
34
- ) => {
35
- const rangeFields = ranges
36
- .map(
37
- ({ from, to }, index) =>
38
- `range_${index}: contributionsCollection(from: "${toGitHubDateTime(from)}", to: "${toGitHubDateTime(to)}") { ...RangeContributionsByRepo }`,
39
- )
40
- .join('\n');
41
-
33
+ ): GraphQLDocument<ReposContributedToQuery, ReposContributedToQueryVariables> => {
42
34
  // `repositoryContributions` only ever returns repos the user owns,
43
35
  // so it is left out rather than gated with @include: an excluded field still counts toward the query's node cost.
44
36
  // stats.graphql carries the directive so the generated type marks the field optional.
@@ -56,7 +48,7 @@ const buildReposContributedToDocument = (
56
48
  return graphqlDocument<ReposContributedToQuery, ReposContributedToQueryVariables>(`
57
49
  query userReposContributedTo($login: String!, $maxRepositories: Int!) {
58
50
  user(login: $login) {
59
- ${rangeFields}
51
+ ${aliasedRanges(ranges, 'RangeContributionsByRepo')}
60
52
  }
61
53
  }
62
54
  fragment RangeContributionsByRepo on ContributionsCollection {
@@ -64,16 +56,25 @@ fragment RangeContributionsByRepo on ContributionsCollection {
64
56
  repository {
65
57
  nameWithOwner
66
58
  }
59
+ contributions {
60
+ totalCount
61
+ }
67
62
  }
68
63
  issueContributionsByRepository(maxRepositories: $maxRepositories) {
69
64
  repository {
70
65
  nameWithOwner
71
66
  }
67
+ contributions {
68
+ totalCount
69
+ }
72
70
  }
73
71
  pullRequestContributionsByRepository(maxRepositories: $maxRepositories) {
74
72
  repository {
75
73
  nameWithOwner
76
74
  }
75
+ contributions {
76
+ totalCount
77
+ }
77
78
  }
78
79
  ${ownRepoField}
79
80
  }`);