@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stats-forge/github-stats-forge-core",
3
- "version": "0.0.2",
3
+ "version": "0.2.0",
4
4
  "description": "Dynamically generate stats for your GitHub readme",
5
5
  "keywords": [
6
6
  "card-generator",
@@ -43,7 +43,7 @@
43
43
  ],
44
44
  "repository": {
45
45
  "type": "git",
46
- "url": "https://github.com/stats-forge/github-stats-forge.git",
46
+ "url": "git+https://github.com/stats-forge/github-stats-forge.git",
47
47
  "directory": "packages/core"
48
48
  },
49
49
  "files": [
@@ -52,20 +52,11 @@
52
52
  "README.md"
53
53
  ],
54
54
  "type": "module",
55
- "main": "./build/index.js",
56
55
  "exports": {
57
- ".": {
58
- "@stats/source": "./src/index.ts",
59
- "default": "./build/index.js"
60
- },
61
56
  "./api": {
62
57
  "@stats/source": "./src/api/index.ts",
63
58
  "default": "./build/api/index.js"
64
59
  },
65
- "./cards": {
66
- "@stats/source": "./src/cards/index.ts",
67
- "default": "./build/cards/index.js"
68
- },
69
60
  "./fetchers": {
70
61
  "@stats/source": "./src/fetchers/index.ts",
71
62
  "default": "./build/fetchers/index.js"
@@ -79,36 +70,36 @@
79
70
  "access": "public"
80
71
  },
81
72
  "dependencies": {
82
- "emoji-name-map": "^2.0.3",
83
- "github-username-regex": "^1.0.0",
84
- "zod": "^4.4.3"
73
+ "zod": "^4.5.4"
85
74
  },
86
75
  "devDependencies": {
87
76
  "@graphql-codegen/core": "6.2.0",
88
77
  "@graphql-codegen/typescript": "6.1.0",
89
- "@graphql-codegen/typescript-operations": "6.1.2",
78
+ "@graphql-codegen/typescript-operations": "6.1.6",
90
79
  "@octokit/graphql-schema": "15.26.1",
91
80
  "@testing-library/dom": "10.4.1",
92
- "@testing-library/jest-dom": "6.9.1",
81
+ "@testing-library/jest-dom": "7.0.1",
82
+ "@types/node": "24.13.3",
93
83
  "@uppercod/css-to-object": "1.1.1",
94
- "graphql": "16.11.0",
95
- "js-yaml": "5.2.1",
96
- "jsdom": "29.1.1",
97
- "vitest": "4.1.10"
84
+ "graphql": "16.14.2",
85
+ "js-yaml": "5.4.1",
86
+ "jsdom": "30.0.1",
87
+ "vitest": "4.1.11"
98
88
  },
99
89
  "engines": {
100
- "node": ">=24"
90
+ "node": "^24 || >=26"
101
91
  },
102
92
  "scripts": {
103
93
  "build": "tsc -p tsconfig.build.json",
94
+ "lint:publish": "attw --pack . --profile node16 --ignore-rules cjs-resolves-to-esm && publint --strict",
104
95
  "test": "vitest",
105
96
  "test:update:snapshot": "vitest -u",
106
97
  "test:e2e": "vitest --config vitest.config.e2e.ts",
107
98
  "bench": "vitest bench --run --config vitest.config.bench.ts",
108
- "lint": "eslint",
109
- "typecheck": "tsc -p tsconfig.typecheck.json",
110
- "generate-graphql-types": "node scripts/generate-graphql-types",
111
- "check-graphql-types": "node scripts/generate-graphql-types --check",
112
- "generate-language-colors": "node scripts/generate-language-colors"
99
+ "typecheck": "tsc -p tsconfig.typecheck.json && tsc -p tsconfig.scripts.json",
100
+ "generate-graphql-types": "node scripts/generate-graphql-types.ts",
101
+ "check-graphql-types": "node scripts/generate-graphql-types.ts --check",
102
+ "generate-language-colors": "node scripts/generate-language-colors.ts",
103
+ "generate-emoji-map": "node scripts/generate-emoji-map.ts"
113
104
  }
114
105
  }
@@ -1,7 +1,7 @@
1
- import type { ColorParams } from '../common/color.js';
2
- import type { ErrorCode } from '../common/error.js';
3
- import { CardError } from '../common/error.js';
4
- import { renderError } from '../common/render.js';
1
+ import type { ColorParams } from '../common/color.ts';
2
+ import type { ErrorCode } from '../common/error.ts';
3
+ import { CardError } from '../common/error.ts';
4
+ import { renderError } from '../common/render.ts';
5
5
 
6
6
  /**
7
7
  * Why a card could not be rendered, in a form a host can act on.
@@ -37,14 +37,15 @@ export type ApiResult =
37
37
 
38
38
  /**
39
39
  * Turns anything a handler threw into the one shape it answers with.
40
+ * Anything that is not a `CardError` is treated as upstream's.
40
41
  *
41
- * @param err The failure.
42
- * Anything that is not a `CardError` is treated as upstream's.
43
- * @param renderOptions Colors the error card is drawn with;
44
- * omitted when it was a color that was rejected.
45
42
  * @returns The rendered error.
46
43
  */
47
- export const errorResult = (err: unknown, renderOptions?: ColorParams): ApiResult => {
44
+ export const errorResult = (
45
+ err: unknown,
46
+ /** omitted when it was a color that was rejected, which cannot draw its own error card */
47
+ renderOptions?: ColorParams,
48
+ ): ApiResult => {
48
49
  const error = CardError.from(err);
49
50
 
50
51
  return {
@@ -0,0 +1,84 @@
1
+ import * as z from 'zod/mini';
2
+
3
+ import { renderContributedToCard } from '../cards/contributed-to.ts';
4
+ import { fetchContributedTo } from '../fetchers/contributed-to.ts';
5
+
6
+ import { cardHandler } from './handler.ts';
7
+ import {
8
+ booleanParam,
9
+ fromParam,
10
+ localeParam,
11
+ looseIntParam,
12
+ numberParam,
13
+ ORDERED_RANGE,
14
+ rawParam,
15
+ safeListParam,
16
+ toParam,
17
+ usernameParam,
18
+ } from './params.ts';
19
+
20
+ /** What the contributed-to endpoint accepts, on top of the shared color params. */
21
+ const contributedToQuery = z
22
+ .object({
23
+ username: usernameParam,
24
+ locale: localeParam,
25
+ repos_count: looseIntParam,
26
+ include_own_repos: booleanParam,
27
+ exclude_repo: safeListParam,
28
+ from: fromParam,
29
+ to: toParam,
30
+ hide_years: booleanParam,
31
+ hide_title: booleanParam,
32
+ hide_border: booleanParam,
33
+ card_width: looseIntParam,
34
+ custom_title: rawParam,
35
+ border_radius: numberParam,
36
+ disable_animations: booleanParam,
37
+ })
38
+ .check(ORDERED_RANGE);
39
+
40
+ /**
41
+ * Render the contributed-to card for a set of query params.
42
+ *
43
+ * @returns The rendered card, or a rendered error.
44
+ */
45
+ export const contributedTo = cardHandler(
46
+ contributedToQuery,
47
+ async (
48
+ {
49
+ username,
50
+ locale,
51
+ repos_count,
52
+ include_own_repos,
53
+ exclude_repo,
54
+ from,
55
+ to,
56
+ hide_years,
57
+ hide_title,
58
+ hide_border,
59
+ card_width,
60
+ custom_title,
61
+ border_radius,
62
+ disable_animations,
63
+ },
64
+ colors,
65
+ config,
66
+ ) => {
67
+ const data = await fetchContributedTo(
68
+ { username, include_own_repos, repos_count, exclude_repo, from, to },
69
+ config,
70
+ );
71
+
72
+ return renderContributedToCard(data, {
73
+ ...colors,
74
+ locale,
75
+ hide_years,
76
+ hide_title,
77
+ hide_border,
78
+ card_width,
79
+ custom_title,
80
+ border_radius,
81
+ disable_animations,
82
+ });
83
+ },
84
+ );
package/src/api/gist.ts CHANGED
@@ -1,66 +1,42 @@
1
1
  import * as z from 'zod/mini';
2
2
 
3
- import { renderGistCard } from '../cards/gist.js';
4
- import type { CardConfig } from '../common/config.js';
5
- import { fetchGist } from '../fetchers/gist.js';
3
+ import { renderGistCard } from '../cards/gist.ts';
4
+ import { fetchGist } from '../fetchers/gist.ts';
6
5
 
7
- import type { ApiResult } from './api-result.js';
8
- import { errorResult } from './api-result.js';
9
- import type { ApiQuery } from './params.js';
10
- import { booleanParam, numberParam, parseColorParams, parseParams, safeParam } from './params.js';
6
+ import { cardHandler } from './handler.ts';
7
+ import { booleanParam, localeParam, numberParam, safeParam } from './params.ts';
11
8
 
12
9
  /** What the gist endpoint accepts, on top of the shared color params. */
13
10
  const gistQuery = z.object({
14
11
  id: safeParam,
12
+ locale: localeParam,
15
13
  border_radius: numberParam,
16
14
  show_owner: booleanParam,
17
15
  browser_rendering: booleanParam,
18
16
  hide_border: booleanParam,
19
17
  });
20
18
 
21
- /** The query this endpoint accepts, checked against the schema above. */
22
- type GistApiQuery = ApiQuery<typeof gistQuery>;
23
-
24
19
  /**
25
20
  * Render the gist card for a set of query params.
26
21
  *
27
- * @param query Raw query params, plus any of the shared color params.
28
- * @param query.id GitHub gist ID.
29
- * @param query.border_radius Card border radius.
30
- * @param query.show_owner Whether to show the gist owner.
31
- * @param query.browser_rendering Whether the browser wraps the description text.
32
- * @param query.hide_border Whether to hide the card border.
33
- * @param config Deployment config supplying the PAT pool.
34
22
  * @returns The rendered card, or a rendered error.
35
23
  */
36
- export const gist = async (query: GistApiQuery, config: CardConfig): Promise<ApiResult> => {
37
- let colors;
38
- try {
39
- colors = parseColorParams(query);
40
- } catch (err) {
41
- // A rejected color cannot be used to draw its own error card.
42
- return errorResult(err);
43
- }
44
-
45
- try {
46
- const { id, border_radius, show_owner, browser_rendering, hide_border } = parseParams(
47
- gistQuery,
48
- query,
49
- );
50
-
24
+ export const gist = cardHandler(
25
+ gistQuery,
26
+ async (
27
+ { id, locale, border_radius, show_owner, browser_rendering, hide_border },
28
+ colors,
29
+ config,
30
+ ) => {
51
31
  const gistData = await fetchGist({ id }, config);
52
32
 
53
- return {
54
- status: 'success',
55
- content: renderGistCard(gistData, {
56
- ...colors,
57
- border_radius,
58
- show_owner,
59
- browser_rendering,
60
- hide_border,
61
- }),
62
- };
63
- } catch (err) {
64
- return errorResult(err, colors);
65
- }
66
- };
33
+ return renderGistCard(gistData, {
34
+ ...colors,
35
+ locale,
36
+ border_radius,
37
+ show_owner,
38
+ browser_rendering,
39
+ hide_border,
40
+ });
41
+ },
42
+ );
@@ -0,0 +1,42 @@
1
+ import type * as z from 'zod/mini';
2
+
3
+ import type { ColorParams } from '../common/color.ts';
4
+ import type { CardConfig } from '../common/config.ts';
5
+
6
+ import type { ApiResult } from './api-result.ts';
7
+ import { errorResult } from './api-result.ts';
8
+ import type { ApiQuery } from './params.ts';
9
+ import { parseColorParams, parseParams } from './params.ts';
10
+
11
+ /**
12
+ * An endpoint: the schema it accepts, and what it draws from the parsed params.
13
+ *
14
+ * Colors parse first and on their own, because a rejected color cannot be used to draw its own error card.
15
+ * Parsing throws, fetching throws, and one `catch` turns whatever was thrown into the answer.
16
+ *
17
+ * @returns The handler, taking the query the schema describes.
18
+ */
19
+ const cardHandler =
20
+ <TSchema extends z.ZodMiniType>(
21
+ schema: TSchema,
22
+ render: (params: z.output<TSchema>, colors: ColorParams, config: CardConfig) => Promise<string>,
23
+ ) =>
24
+ async (query: ApiQuery<TSchema>, config: CardConfig): Promise<ApiResult> => {
25
+ let colors: ColorParams;
26
+ try {
27
+ colors = parseColorParams(query);
28
+ } catch (error) {
29
+ return errorResult(error);
30
+ }
31
+
32
+ try {
33
+ return {
34
+ status: 'success',
35
+ content: await render(parseParams(schema, query), colors, config),
36
+ };
37
+ } catch (error) {
38
+ return errorResult(error, colors);
39
+ }
40
+ };
41
+
42
+ export { cardHandler };
package/src/api/index.ts CHANGED
@@ -1,16 +1,17 @@
1
- export { gist } from './gist.js';
2
- export { pin } from './pin.js';
3
- export { stats } from './stats.js';
4
- export { topLangs } from './top-langs.js';
5
- export { wakatime } from './wakatime.js';
1
+ export { contributedTo } from './contributed-to.ts';
2
+ export { gist } from './gist.ts';
3
+ export { pin } from './pin.ts';
4
+ export { stats } from './stats.ts';
5
+ export { topLangs } from './top-langs.ts';
6
+ export { wakatime } from './wakatime.ts';
6
7
 
7
- export type { ApiError, ApiResult } from './api-result.js';
8
+ export type { ApiError, ApiResult } from './api-result.ts';
8
9
 
9
10
  // every handler but `wakatime` needs the PAT pool, and that one still needs the transport.
10
- export { CardConfig } from '../common/config.js';
11
- export type { CardConfigInit, PersonalAccessToken } from '../common/config.js';
12
- export type { FetchLike } from '../common/http.js';
13
- export type { ErrorCode } from '../common/error.js';
11
+ export { CardConfig } from '../common/config.ts';
12
+ export type { CardConfigInit, PersonalAccessToken } from '../common/config.ts';
13
+ export type { FetchLike } from '../common/http.ts';
14
+ export type { ErrorCode } from '../common/error.ts';
14
15
 
15
- export { themes } from '../themes/index.js';
16
- export type { ThemeName } from '../themes/index.js';
16
+ export { themes } from '../themes/index.ts';
17
+ export type { ThemeName } from '../themes/index.ts';
package/src/api/params.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import * as z from 'zod/mini';
2
2
 
3
- import type { ColorParams } from '../common/color.js';
4
- import { COLOR_PARAM_KEYS, THEME_PARAM_KEYS, isValidColorInput } from '../common/color.js';
5
- import { CardError } from '../common/error.js';
6
- import { parseArray, parseBoolean } from '../common/ops.js';
7
- import { isLocaleAvailable } from '../translations.js';
3
+ import type { ColorParams } from '../common/color.ts';
4
+ import { COLOR_PARAM_KEYS, THEME_PARAM_KEYS, isValidColorInput } from '../common/color.ts';
5
+ import { GITHUB_USERNAME_PATTERN } from '../common/constants.ts';
6
+ import { getWidestRange, parseRangeDate } from '../common/date.ts';
7
+ import { CardError } from '../common/error.ts';
8
+ import { parseArray, parseBoolean } from '../common/ops.ts';
9
+ import { isLocaleAvailable } from '../translations.ts';
8
10
 
9
11
  /**
10
12
  * @file The api layer is the trust boundary:
@@ -19,11 +21,20 @@ import { isLocaleAvailable } from '../translations.js';
19
21
  /** Every param arrives as a string, or not at all. */
20
22
  const rawParam = z.optional(z.string());
21
23
 
22
- /** Characters a username, repository, owner or gist id may contain. */
24
+ /** Characters a repository, owner or gist id may contain. */
23
25
  const SAFE_PATTERN = /^[-\w/.,]+$/;
24
26
 
25
27
  /** What a check rejected a param for. */
26
- type Rejection = 'number' | 'year' | 'unsafe' | 'locale' | 'enum' | 'color';
28
+ type Rejection =
29
+ | 'number'
30
+ | 'date'
31
+ | 'out_of_range'
32
+ | 'inverted_range'
33
+ | 'unsafe'
34
+ | 'username'
35
+ | 'locale'
36
+ | 'enum'
37
+ | 'color';
27
38
 
28
39
  /**
29
40
  * Every rejection the api can put on an error card, in one place.
@@ -31,8 +42,11 @@ type Rejection = 'number' | 'year' | 'unsafe' | 'locale' | 'enum' | 'color';
31
42
  */
32
43
  const REJECTION_MESSAGES: Record<Rejection, (param: string) => string> = {
33
44
  number: (param) => `Invalid number input for parameter "${param}"`,
34
- year: (param) => `Invalid number input for parameter "${param}"`,
45
+ date: (param) => `Invalid date input for parameter "${param}"`,
46
+ out_of_range: (param) => `Out of range date for parameter "${param}"`,
47
+ inverted_range: () => 'Range "from" is after "to"',
35
48
  unsafe: (param) => `Parameter "${param}" contains unsafe characters`,
49
+ username: (param) => `Invalid username input for parameter "${param}"`,
36
50
  locale: () => 'Locale not found',
37
51
  enum: (param) => `Incorrect ${param} input`,
38
52
  color: (param) => `Invalid color input for parameter "${param}"`,
@@ -42,11 +56,9 @@ const REJECTION_MESSAGES: Record<Rejection, (param: string) => string> = {
42
56
  * A check that words its own rejection:
43
57
  * the message is built from the kind and the param's path rather than passed in.
44
58
  *
45
- * @param kind What the check rejects the param for.
46
- * @param passes Whether a value is acceptable.
47
59
  * @returns The check, ready for `.check()`.
48
60
  */
49
- const rejects = (kind: Rejection, passes: (value: string) => boolean) =>
61
+ const rejects = (kind: Rejection, passes: (value: string) => boolean): z.core.$ZodCheck<unknown> =>
50
62
  z.refine((value: unknown) => typeof value !== 'string' || passes(value), {
51
63
  error: (issue) => REJECTION_MESSAGES[kind](String(issue.path?.[0] ?? '')),
52
64
  });
@@ -72,8 +84,8 @@ const listParam = z.pipe(rawParam, z.transform(parseArray));
72
84
  * Yields the parsed number, or `undefined` when the param is absent.
73
85
  */
74
86
  const numberParam: z.ZodMiniType<number | undefined, string | undefined> = z.pipe(
75
- rawParam.check(rejects('number', (value) => Number.isFinite(parseFloat(value)))),
76
- z.transform((value) => (value === undefined ? undefined : parseFloat(value))),
87
+ rawParam.check(rejects('number', (value) => Number.isFinite(Number.parseFloat(value)))),
88
+ z.transform((value) => (value === undefined ? undefined : Number.parseFloat(value))),
77
89
  );
78
90
 
79
91
  /**
@@ -82,17 +94,42 @@ const numberParam: z.ZodMiniType<number | undefined, string | undefined> = z.pip
82
94
  */
83
95
  const looseIntParam = z.pipe(
84
96
  rawParam,
85
- z.transform((value) => (value === undefined ? undefined : parseInt(value, 10))),
97
+ z.transform((value) => (value === undefined ? undefined : Number.parseInt(value, 10))),
86
98
  );
87
99
 
88
100
  /**
89
- * A four-digit year.
90
- * Anything else builds a `DateTime` GitHub rejects, so it is a permanent error here rather than a failed request later.
91
- * Yields the year, or `undefined` when the param is absent.
101
+ * One end of the range a card counts within, written as `2024`, `2024-03` or `2024-03-15`.
102
+ *
103
+ * @returns Schema yielding the instant, or `undefined` when the param is absent.
92
104
  */
93
- const yearParam: z.ZodMiniType<number | undefined, string | undefined> = z.pipe(
94
- rawParam.check(rejects('year', (value) => /^\d{4}$/.test(value))),
95
- z.transform((value) => (value === undefined ? undefined : Number(value))),
105
+ const rangeParam = (end: 'from' | 'to'): z.ZodMiniType<Date | undefined, string | undefined> => {
106
+ const parse = (value: string): Date | undefined => parseRangeDate(value, end);
107
+ return z.pipe(
108
+ rawParam.check(rejects('date', (value) => parse(value) !== undefined)).check(
109
+ rejects('out_of_range', (value) => {
110
+ const date = parse(value);
111
+ const widest = getWidestRange();
112
+ // a bad shape is already reported by the check above
113
+ return date === undefined || (date >= widest.from && date <= widest.to);
114
+ }),
115
+ ),
116
+ z.transform((value) => (value === undefined ? undefined : parse(value))),
117
+ );
118
+ };
119
+
120
+ /** The start of a card's range; see {@link rangeParam}. */
121
+ const fromParam = rangeParam('from');
122
+
123
+ /** The end of a card's range; see {@link rangeParam}. */
124
+ const toParam = rangeParam('to');
125
+
126
+ /**
127
+ * The pair-level rule the two ends cannot see on their own.
128
+ * Every endpoint taking a range applies it, so an order no card could draw is rejected once.
129
+ */
130
+ const ORDERED_RANGE = z.refine<{ from?: Date | undefined; to?: Date | undefined }>(
131
+ ({ from, to }) => from === undefined || to === undefined || from <= to,
132
+ { error: REJECTION_MESSAGES.inverted_range('from'), path: ['from'] },
96
133
  );
97
134
 
98
135
  /**
@@ -103,6 +140,14 @@ const safeParam: z.ZodMiniType<string | undefined, string | undefined> = rawPara
103
140
  rejects('unsafe', (value) => !value || SAFE_PATTERN.test(value)),
104
141
  );
105
142
 
143
+ /**
144
+ * A GitHub login, stricter than `safeParam`: no leading, trailing or doubled hyphen.
145
+ * Rejected here so a malformed one is a permanent error rather than the NOT_FOUND GitHub answers.
146
+ */
147
+ const usernameParam: z.ZodMiniType<string | undefined, string | undefined> = rawParam.check(
148
+ rejects('username', (value) => !value || GITHUB_USERNAME_PATTERN.test(value)),
149
+ );
150
+
106
151
  /**
107
152
  * A comma-separated list of ids, checked before it is split.
108
153
  * The safe pattern allows the commas, so one check covers the whole list.
@@ -126,7 +171,6 @@ const localeParam = z.pipe(
126
171
  /**
127
172
  * A param the card only renders as one of a fixed set of values.
128
173
  *
129
- * @param values The accepted values, as the card that renders them declares them.
130
174
  * @returns Schema yielding one of `values`, or `undefined` when absent.
131
175
  */
132
176
  const enumParam = <const T extends ReadonlyArray<string>>(
@@ -163,11 +207,10 @@ type ApiQuery<TSchema extends z.ZodMiniType> = Partial<z.input<TSchema>> & Color
163
207
  * Every rejection is the query's fault, so they share one code
164
208
  * and differ only in the message the check already worded.
165
209
  *
166
- * @param error What the schema rejected.
167
210
  * @returns The failure, ready to render.
168
211
  */
169
212
  const toCardError = (error: z.core.$ZodError): CardError => {
170
- const issue = error.issues[0];
213
+ const [issue] = error.issues;
171
214
  return CardError.invalidParam(String(issue?.path[0] ?? ''), issue?.message ?? 'Invalid input');
172
215
  };
173
216
 
@@ -177,10 +220,9 @@ const toCardError = (error: z.core.$ZodError): CardError => {
177
220
  * Only the first rejection is reported:
178
221
  * the error card has room for one line.
179
222
  *
180
- * @param schema The endpoint's schema.
181
- * @param query Raw query params.
182
- * @returns The parsed params.
183
223
  * @throws {CardError} When the schema rejects a param.
224
+ *
225
+ * @returns The parsed params.
184
226
  */
185
227
  const parseParams = <TSchema extends z.ZodMiniType>(
186
228
  schema: TSchema,
@@ -197,9 +239,9 @@ const parseParams = <TSchema extends z.ZodMiniType>(
197
239
  * The color params, validated.
198
240
  * Split from the endpoint's own schema because a rejected color cannot then be used to render its own error card.
199
241
  *
200
- * @param query Raw query params.
201
- * @returns The color params.
202
242
  * @throws {CardError} When a param does not hold a color or a gradient.
243
+ *
244
+ * @returns The color params.
203
245
  */
204
246
  const parseColorParams = (query: unknown): ColorParams => parseParams(colorParamsSchema, query);
205
247
 
@@ -208,7 +250,9 @@ export type { ApiQuery };
208
250
  export {
209
251
  booleanParam,
210
252
  enumParam,
253
+ fromParam,
211
254
  listParam,
255
+ ORDERED_RANGE,
212
256
  localeParam,
213
257
  looseIntParam,
214
258
  numberParam,
@@ -217,5 +261,6 @@ export {
217
261
  rawParam,
218
262
  safeListParam,
219
263
  safeParam,
220
- yearParam,
264
+ toParam,
265
+ usernameParam,
221
266
  };