@stats-forge/github-stats-forge-core 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/api/contributed-to.d.ts +23 -0
- package/build/api/contributed-to.d.ts.map +1 -0
- package/build/api/contributed-to.js +43 -0
- package/build/api/gist.d.ts +8 -15
- package/build/api/gist.d.ts.map +1 -1
- package/build/api/gist.js +14 -29
- package/build/api/handler.d.ts +16 -0
- package/build/api/handler.d.ts.map +1 -0
- package/build/api/handler.js +29 -0
- package/build/api/index.d.ts +1 -0
- package/build/api/index.d.ts.map +1 -1
- package/build/api/index.js +1 -0
- package/build/api/params.d.ts +11 -5
- package/build/api/params.d.ts.map +1 -1
- package/build/api/params.js +26 -6
- package/build/api/pin.d.ts +11 -15
- package/build/api/pin.d.ts.map +1 -1
- package/build/api/pin.js +34 -46
- package/build/api/stats.d.ts +15 -22
- package/build/api/stats.d.ts.map +1 -1
- package/build/api/stats.js +59 -72
- package/build/api/top-langs.d.ts +11 -22
- package/build/api/top-langs.d.ts.map +1 -1
- package/build/api/top-langs.js +34 -51
- package/build/api/wakatime.d.ts +10 -24
- package/build/api/wakatime.d.ts.map +1 -1
- package/build/api/wakatime.js +27 -47
- package/build/cards/contributed-to.d.ts +19 -0
- package/build/cards/contributed-to.d.ts.map +1 -0
- package/build/cards/contributed-to.js +242 -0
- package/build/cards/gist.d.ts +1 -0
- package/build/cards/gist.d.ts.map +1 -1
- package/build/cards/gist.js +13 -9
- package/build/cards/repo.d.ts +12 -1
- package/build/cards/repo.d.ts.map +1 -1
- package/build/cards/repo.js +40 -18
- package/build/cards/stats.d.ts +15 -3
- package/build/cards/stats.d.ts.map +1 -1
- package/build/cards/stats.js +91 -76
- package/build/cards/top-languages.d.ts +13 -2
- package/build/cards/top-languages.d.ts.map +1 -1
- package/build/cards/top-languages.js +26 -9
- package/build/cards/wakatime.d.ts +13 -2
- package/build/cards/wakatime.d.ts.map +1 -1
- package/build/cards/wakatime.js +22 -9
- package/build/common/Card.d.ts +9 -0
- package/build/common/Card.d.ts.map +1 -1
- package/build/common/Card.js +44 -9
- package/build/common/I18n.d.ts +1 -1
- package/build/common/I18n.d.ts.map +1 -1
- package/build/common/I18n.js +4 -2
- package/build/common/brand.d.ts +76 -0
- package/build/common/brand.d.ts.map +1 -0
- package/build/common/brand.js +73 -0
- package/build/common/constants.d.ts +3 -1
- package/build/common/constants.d.ts.map +1 -1
- package/build/common/constants.js +3 -1
- package/build/common/date.d.ts +50 -3
- package/build/common/date.d.ts.map +1 -1
- package/build/common/date.js +104 -2
- package/build/common/icons.d.ts +4 -2
- package/build/common/icons.d.ts.map +1 -1
- package/build/common/icons.js +10 -5
- package/build/common/ops.d.ts +8 -3
- package/build/common/ops.d.ts.map +1 -1
- package/build/common/ops.js +8 -14
- package/build/common/render.d.ts +7 -1
- package/build/common/render.d.ts.map +1 -1
- package/build/common/render.js +69 -61
- package/build/fetchers/contributed-to.d.ts +62 -0
- package/build/fetchers/contributed-to.d.ts.map +1 -0
- package/build/fetchers/contributed-to.js +184 -0
- package/build/fetchers/graphql-error.d.ts +13 -0
- package/build/fetchers/graphql-error.d.ts.map +1 -0
- package/build/fetchers/graphql-error.js +29 -0
- package/build/fetchers/index.d.ts +2 -1
- package/build/fetchers/index.d.ts.map +1 -1
- package/build/fetchers/index.js +1 -0
- package/build/fetchers/stats.d.ts +14 -8
- package/build/fetchers/stats.d.ts.map +1 -1
- package/build/fetchers/stats.js +72 -195
- package/build/fetchers/top-languages.d.ts.map +1 -1
- package/build/fetchers/top-languages.js +3 -18
- package/build/fetchers/types.d.ts +24 -0
- package/build/fetchers/types.d.ts.map +1 -1
- package/build/graphql/contributionsCollection.d.ts +9 -0
- package/build/graphql/contributionsCollection.d.ts.map +1 -0
- package/build/graphql/contributionsCollection.js +12 -0
- package/build/graphql/contributionsDocument.d.ts +5 -5
- package/build/graphql/contributionsDocument.d.ts.map +1 -1
- package/build/graphql/contributionsDocument.js +8 -15
- package/build/graphql/generated/contributed-to.d.ts +22 -0
- package/build/graphql/generated/contributed-to.d.ts.map +1 -0
- package/build/graphql/generated/contributed-to.js +11 -0
- package/build/graphql/generated/stats.d.ts +13 -1
- package/build/graphql/generated/stats.d.ts.map +1 -1
- package/build/graphql/generated/stats.js +2 -2
- package/build/graphql/graphqlDocument.d.ts +1 -1
- package/build/graphql/reposContributedToDocument.d.ts.map +1 -1
- package/build/graphql/reposContributedToDocument.js +11 -5
- package/build/translations.d.ts +64 -1
- package/build/translations.d.ts.map +1 -1
- package/build/translations.js +65 -1
- package/package.json +1 -1
- package/src/api/contributed-to.ts +84 -0
- package/src/api/gist.ts +20 -37
- package/src/api/handler.ts +42 -0
- package/src/api/index.ts +1 -0
- package/src/api/params.ts +48 -9
- package/src/api/pin.ts +33 -45
- package/src/api/stats.ts +78 -89
- package/src/api/top-langs.ts +34 -56
- package/src/api/wakatime.ts +33 -54
- package/src/cards/contributed-to.ts +373 -0
- package/src/cards/gist.ts +17 -8
- package/src/cards/repo.ts +44 -20
- package/src/cards/stats.ts +119 -90
- package/src/cards/top-languages.ts +27 -18
- package/src/cards/wakatime.ts +23 -13
- package/src/common/Card.ts +56 -11
- package/src/common/I18n.ts +4 -2
- package/src/common/brand.ts +109 -0
- package/src/common/constants.ts +4 -1
- package/src/common/date.ts +135 -3
- package/src/common/icons.ts +10 -5
- package/src/common/ops.ts +10 -19
- package/src/common/render.ts +75 -65
- package/src/fetchers/contributed-to.ts +271 -0
- package/src/fetchers/graphql-error.ts +36 -0
- package/src/fetchers/index.ts +3 -0
- package/src/fetchers/stats.ts +123 -274
- package/src/fetchers/top-languages.ts +5 -19
- package/src/fetchers/types.ts +26 -0
- package/src/graphql/contributionsCollection.ts +18 -0
- package/src/graphql/contributionsDocument.ts +11 -18
- package/src/graphql/generated/contributed-to.ts +25 -0
- package/src/graphql/generated/stats.ts +19 -6
- package/src/graphql/graphqlDocument.ts +1 -1
- package/src/graphql/queries/contributed-to.graphql +8 -0
- package/src/graphql/queries/stats.graphql +14 -2
- package/src/graphql/reposContributedToDocument.ts +11 -9
- package/src/translations.ts +77 -0
package/build/common/ops.js
CHANGED
|
@@ -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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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, };
|
package/build/common/render.d.ts
CHANGED
|
@@ -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":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAQ7E;;;;;GAKG;AACH,QAAA,MAAM,UAAU,sCAKb;IACD,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAC;IAC7B,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACvB,KAAG,KAAK,CAAC,KAAK,CA2Bd,CAAC;AAEF;;;;GAIG;AACH,QAAA,MAAM,kBAAkB,aAAc,MAAM,aAAa,MAAM,KAAG,aAWjE,CAAC;AAEF;;;;GAIG;AACH,QAAA,MAAM,kBAAkB,6CAOrB;IACD,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf,KAAG,aA+CH,CAAC;AAEF;;;;;;GAMG;AACH,QAAA,MAAM,eAAe,iEASlB;IACD,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,KAAG,aA+BH,CAAC;AAEF;;;;;;;GAOG;AACH,QAAA,MAAM,iBAAiB,UAAW,MAAM,KAAG,YAmB1C,CAAC;AAEF;;;;;GAKG;AACH,QAAA,MAAM,aAAa,SACX,KAAK,SACJ,MAAM,GAAG,MAAM,UACd,MAAM,YACJ,MAAM,KACf,KA2BF,CAAC;AAEF;;;;;;;;GAQG;AACH,QAAA,MAAM,cAAc,+JAgBjB;IACD,IAAI,EAAE,KAAK,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,EAAE,EAAE,MAAM,CAAC;IAEX,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,KAAG,aAsEH,CAAC;AAMF;;;;GAIG;AACH,QAAA,MAAM,WAAW,kDAId;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAEtC,aAAa,CAAC,EAAE;QACd,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QACjC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAChC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAC9B,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAClC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAC3B,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;KACtC,CAAC;CACH,KAAG,
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/common/render.ts"],"names":[],"mappings":"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"}
|
package/build/common/render.js
CHANGED
|
@@ -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).
|
|
@@ -246,9 +252,14 @@ const renderError = ({ message, secondaryMessage = '', renderOptions = {}, }) =>
|
|
|
246
252
|
ring_color: '',
|
|
247
253
|
theme,
|
|
248
254
|
});
|
|
249
|
-
|
|
255
|
+
/*
|
|
256
|
+
* Its own line, at the small size, because it no longer fits beside the title: the inherited URL
|
|
257
|
+
* ran to 561px inside a 576.5px card and this one reaches 587px. Measured in a canvas at the
|
|
258
|
+
* title's own font, so re-measure before putting it back on one line.
|
|
259
|
+
*/
|
|
260
|
+
const report = UPSTREAM_API_ERRORS.has(secondaryMessage) || !show_repo_link
|
|
250
261
|
? ''
|
|
251
|
-
: '
|
|
262
|
+
: 'File an issue at https://tinyurl.com/stats-forge-bug';
|
|
252
263
|
return renderMarkup(el('svg', {
|
|
253
264
|
width: ERROR_CARD_LENGTH,
|
|
254
265
|
height: 120,
|
|
@@ -256,8 +267,8 @@ const renderError = ({ message, secondaryMessage = '', renderOptions = {}, }) =>
|
|
|
256
267
|
fill: String(bgColor),
|
|
257
268
|
xmlns: 'http://www.w3.org/2000/svg',
|
|
258
269
|
role: 'img',
|
|
259
|
-
'aria-labelledby': '
|
|
260
|
-
}, el('title', { id: '
|
|
270
|
+
'aria-labelledby': 'title-id desc-id',
|
|
271
|
+
}, 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
272
|
x: 0.5,
|
|
262
273
|
y: 0.5,
|
|
263
274
|
width: ERROR_CARD_LENGTH - 1,
|
|
@@ -265,8 +276,52 @@ const renderError = ({ message, secondaryMessage = '', renderOptions = {}, }) =>
|
|
|
265
276
|
rx: 8,
|
|
266
277
|
fill: String(bgColor),
|
|
267
278
|
stroke: borderColor,
|
|
268
|
-
}), el('text', { x: 25, y: 45, class: 'text' },
|
|
279
|
+
}), el('text', { x: 25, y: 45, class: 'text' }, 'Something went wrong!'), 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)), report !== '' &&
|
|
280
|
+
el('text', { 'data-testid': 'report', x: 25, y: 109, class: 'text small gray' }, report)));
|
|
269
281
|
};
|
|
282
|
+
/** Advance of each ASCII code point, as a fraction of the font size. */
|
|
283
|
+
// prettier-ignore
|
|
284
|
+
const CHAR_WIDTHS = [
|
|
285
|
+
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,
|
|
286
|
+
0, 0, 0, 0, 0, 0, 0, 0.2733333110809326, 0.28499999046325686,
|
|
287
|
+
0.39166667461395266, 0.5900000095367431, 0.5383333206176758,
|
|
288
|
+
0.8183333396911621, 0.8, 0.22999999523162842, 0.30166666507720946,
|
|
289
|
+
0.30166666507720946, 0.41666665077209475, 0.6833333492279052,
|
|
290
|
+
0.21666667461395264, 0.4, 0.21666667461395264, 0.3900000095367432,
|
|
291
|
+
0.5383333206176758, 0.5383333206176758, 0.5383333206176758,
|
|
292
|
+
0.5383333206176758, 0.5383333206176758, 0.5383333206176758,
|
|
293
|
+
0.5383333206176758, 0.5383333206176758, 0.5383333206176758,
|
|
294
|
+
0.5383333206176758, 0.21666667461395264, 0.21666667461395264,
|
|
295
|
+
0.6833333492279052, 0.6833333492279052, 0.6833333492279052,
|
|
296
|
+
0.4483333110809326, 0.9550000190734863, 0.6449999809265137,
|
|
297
|
+
0.5733333110809327, 0.6183333396911621, 0.7016666889190674,
|
|
298
|
+
0.5066666603088379, 0.48833332061767576, 0.6866666793823242,
|
|
299
|
+
0.7099999904632568, 0.26666667461395266, 0.35666666030883787,
|
|
300
|
+
0.5800000190734863, 0.4699999809265137, 0.8983333587646485,
|
|
301
|
+
0.7483333110809326, 0.753333330154419, 0.5599999904632569,
|
|
302
|
+
0.753333330154419, 0.5983333110809326, 0.5316666603088379,
|
|
303
|
+
0.5233333110809326, 0.6866666793823242, 0.621666669845581,
|
|
304
|
+
0.9333333015441895, 0.5900000095367431, 0.553333330154419,
|
|
305
|
+
0.5699999809265137, 0.30166666507720946, 0.37833333015441895,
|
|
306
|
+
0.30166666507720946, 0.6833333492279052, 0.41500000953674315,
|
|
307
|
+
0.26833333969116213, 0.5083333492279053, 0.5883333206176757,
|
|
308
|
+
0.4616666793823242, 0.5883333206176757, 0.5233333110809326,
|
|
309
|
+
0.3133333444595337, 0.5883333206176757, 0.5666666507720948,
|
|
310
|
+
0.24166667461395264, 0.24166667461395264, 0.49666666984558105,
|
|
311
|
+
0.24166667461395264, 0.8616666793823242, 0.5666666507720948,
|
|
312
|
+
0.5866666793823242, 0.5883333206176757, 0.5883333206176757,
|
|
313
|
+
0.3483333349227905, 0.425, 0.33833334445953367, 0.5666666507720948,
|
|
314
|
+
0.4783333301544189, 0.7233333110809326, 0.45833334922790525,
|
|
315
|
+
0.4833333492279053, 0.45166668891906736, 0.30166666507720946,
|
|
316
|
+
0.24000000953674316, 0.30166666507720946, 0.6833333492279052,
|
|
317
|
+
];
|
|
318
|
+
/** Advance of a code point the table does not cover. */
|
|
319
|
+
const AVERAGE_CHAR_WIDTH = 0.5131403493881227;
|
|
320
|
+
/**
|
|
321
|
+
* Full-width glyphs: U+3000–U+9FFF (CJK symbols and punctuation, Hiragana, Katakana,
|
|
322
|
+
* CJK Unified Ideographs incl. Extension A) plus U+FF00–U+FFEF (halfwidth and fullwidth forms).
|
|
323
|
+
*/
|
|
324
|
+
const FULL_WIDTH_PATTERN = /[\u3000-\u9FFF\uFF00-\uFFEF]/;
|
|
270
325
|
/**
|
|
271
326
|
* Retrieve text length based on Segoe UI font.
|
|
272
327
|
*
|
|
@@ -274,61 +329,14 @@ const renderError = ({ message, secondaryMessage = '', renderOptions = {}, }) =>
|
|
|
274
329
|
*
|
|
275
330
|
* @returns Text length.
|
|
276
331
|
*/
|
|
277
|
-
const measureText = (str, fontSize = 10) =>
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
};
|
|
332
|
+
const measureText = (str, fontSize = 10) => [...str]
|
|
333
|
+
.map((char) => {
|
|
334
|
+
if (FULL_WIDTH_PATTERN.test(char)) {
|
|
335
|
+
return 1;
|
|
336
|
+
}
|
|
337
|
+
return CHAR_WIDTHS[char.codePointAt(0) ?? 0] ?? AVERAGE_CHAR_WIDTH;
|
|
338
|
+
})
|
|
339
|
+
.reduce((total, width) => total + width, 0) * fontSize;
|
|
332
340
|
/**
|
|
333
341
|
* Split text into the lines it would wrap to when laid out greedily at the
|
|
334
342
|
* given font size inside a box of width `maxWidth`. Uses `measureText` so the
|
|
@@ -435,4 +443,4 @@ const wrapTextMultiline = (text, width, fontSize, maxLines = 3) => {
|
|
|
435
443
|
* @returns Estimated line count, at least 1, at most `maxLines`.
|
|
436
444
|
*/
|
|
437
445
|
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, };
|
|
446
|
+
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"}
|
package/build/fetchers/index.js
CHANGED
|
@@ -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,
|
|
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,
|
|
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
|
-
|
|
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;
|
|
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"}
|