@stats-forge/github-stats-forge-core 0.0.1
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/LICENSE +21 -0
- package/README.md +148 -0
- package/build/api/api-result.d.ts +44 -0
- package/build/api/api-result.d.ts.map +1 -0
- package/build/api/api-result.js +33 -0
- package/build/api/gist.d.ts +29 -0
- package/build/api/gist.d.ts.map +1 -0
- package/build/api/gist.js +52 -0
- package/build/api/index.d.ts +13 -0
- package/build/api/index.d.ts.map +1 -0
- package/build/api/index.js +8 -0
- package/build/api/params.d.ts +93 -0
- package/build/api/params.d.ts.map +1 -0
- package/build/api/params.js +145 -0
- package/build/api/pin.d.ts +47 -0
- package/build/api/pin.d.ts.map +1 -0
- package/build/api/pin.js +87 -0
- package/build/api/stats.d.ts +42 -0
- package/build/api/stats.d.ts.map +1 -0
- package/build/api/stats.js +132 -0
- package/build/api/top-langs.d.ts +37 -0
- package/build/api/top-langs.d.ts.map +1 -0
- package/build/api/top-langs.js +101 -0
- package/build/api/wakatime.d.ts +34 -0
- package/build/api/wakatime.d.ts.map +1 -0
- package/build/api/wakatime.js +91 -0
- package/build/calculateRank.d.ts +29 -0
- package/build/calculateRank.d.ts.map +1 -0
- package/build/calculateRank.js +58 -0
- package/build/cards/gist.d.ts +16 -0
- package/build/cards/gist.d.ts.map +1 -0
- package/build/cards/gist.js +119 -0
- package/build/cards/index.d.ts +11 -0
- package/build/cards/index.d.ts.map +1 -0
- package/build/cards/index.js +7 -0
- package/build/cards/options.d.ts +19 -0
- package/build/cards/options.d.ts.map +1 -0
- package/build/cards/options.js +1 -0
- package/build/cards/repo.d.ts +25 -0
- package/build/cards/repo.d.ts.map +1 -0
- package/build/cards/repo.js +276 -0
- package/build/cards/stats.d.ts +37 -0
- package/build/cards/stats.d.ts.map +1 -0
- package/build/cards/stats.js +457 -0
- package/build/cards/top-languages.d.ts +153 -0
- package/build/cards/top-languages.d.ts.map +1 -0
- package/build/cards/top-languages.js +805 -0
- package/build/cards/wakatime.d.ts +31 -0
- package/build/cards/wakatime.d.ts.map +1 -0
- package/build/cards/wakatime.js +376 -0
- package/build/common/Card.d.ts +121 -0
- package/build/common/Card.d.ts.map +1 -0
- package/build/common/Card.js +305 -0
- package/build/common/I18n.d.ts +27 -0
- package/build/common/I18n.d.ts.map +1 -0
- package/build/common/I18n.js +35 -0
- package/build/common/color.d.ts +112 -0
- package/build/common/color.d.ts.map +1 -0
- package/build/common/color.js +188 -0
- package/build/common/config.d.ts +53 -0
- package/build/common/config.d.ts.map +1 -0
- package/build/common/config.js +87 -0
- package/build/common/constants.d.ts +7 -0
- package/build/common/constants.d.ts.map +1 -0
- package/build/common/constants.js +9 -0
- package/build/common/date.d.ts +29 -0
- package/build/common/date.d.ts.map +1 -0
- package/build/common/date.js +23 -0
- package/build/common/error.d.ts +87 -0
- package/build/common/error.d.ts.map +1 -0
- package/build/common/error.js +108 -0
- package/build/common/fmt.d.ts +28 -0
- package/build/common/fmt.d.ts.map +1 -0
- package/build/common/fmt.js +68 -0
- package/build/common/html.d.ts +8 -0
- package/build/common/html.d.ts.map +1 -0
- package/build/common/html.js +14 -0
- package/build/common/http.d.ts +53 -0
- package/build/common/http.d.ts.map +1 -0
- package/build/common/http.js +47 -0
- package/build/common/icons.d.ts +28 -0
- package/build/common/icons.d.ts.map +1 -0
- package/build/common/icons.js +77 -0
- package/build/common/languageColors.d.ts +11 -0
- package/build/common/languageColors.d.ts.map +1 -0
- package/build/common/languageColors.js +15 -0
- package/build/common/languageColors.json +678 -0
- package/build/common/log.d.ts +9 -0
- package/build/common/log.d.ts.map +1 -0
- package/build/common/log.js +8 -0
- package/build/common/ops.d.ts +59 -0
- package/build/common/ops.d.ts.map +1 -0
- package/build/common/ops.js +115 -0
- package/build/common/render.d.ts +196 -0
- package/build/common/render.d.ts.map +1 -0
- package/build/common/render.js +474 -0
- package/build/common/retryer.d.ts +36 -0
- package/build/common/retryer.d.ts.map +1 -0
- package/build/common/retryer.js +67 -0
- package/build/fetchers/gist.d.ts +15 -0
- package/build/fetchers/gist.d.ts.map +1 -0
- package/build/fetchers/gist.js +68 -0
- package/build/fetchers/index.d.ts +13 -0
- package/build/fetchers/index.d.ts.map +1 -0
- package/build/fetchers/index.js +10 -0
- package/build/fetchers/repo.d.ts +27 -0
- package/build/fetchers/repo.d.ts.map +1 -0
- package/build/fetchers/repo.js +100 -0
- package/build/fetchers/stats.d.ts +74 -0
- package/build/fetchers/stats.d.ts.map +1 -0
- package/build/fetchers/stats.js +498 -0
- package/build/fetchers/top-languages.d.ts +23 -0
- package/build/fetchers/top-languages.d.ts.map +1 -0
- package/build/fetchers/top-languages.js +89 -0
- package/build/fetchers/types.d.ts +130 -0
- package/build/fetchers/types.d.ts.map +1 -0
- package/build/fetchers/types.js +1 -0
- package/build/fetchers/wakatime.d.ts +19 -0
- package/build/fetchers/wakatime.d.ts.map +1 -0
- package/build/fetchers/wakatime.js +36 -0
- package/build/graphql/contributionsDocument.d.ts +18 -0
- package/build/graphql/contributionsDocument.d.ts.map +1 -0
- package/build/graphql/contributionsDocument.js +30 -0
- package/build/graphql/generated/common.d.ts +42 -0
- package/build/graphql/generated/common.d.ts.map +1 -0
- package/build/graphql/generated/common.js +2 -0
- package/build/graphql/generated/gist.d.ts +44 -0
- package/build/graphql/generated/gist.d.ts.map +1 -0
- package/build/graphql/generated/gist.js +27 -0
- package/build/graphql/generated/repo.d.ts +67 -0
- package/build/graphql/generated/repo.d.ts.map +1 -0
- package/build/graphql/generated/repo.js +30 -0
- package/build/graphql/generated/stats.d.ts +150 -0
- package/build/graphql/generated/stats.d.ts.map +1 -0
- package/build/graphql/generated/stats.js +95 -0
- package/build/graphql/generated/top-languages.d.ts +54 -0
- package/build/graphql/generated/top-languages.d.ts.map +1 -0
- package/build/graphql/generated/top-languages.js +27 -0
- package/build/graphql/graphqlDocument.d.ts +22 -0
- package/build/graphql/graphqlDocument.d.ts.map +1 -0
- package/build/graphql/graphqlDocument.js +8 -0
- package/build/graphql/reposContributedToDocument.d.ts +27 -0
- package/build/graphql/reposContributedToDocument.d.ts.map +1 -0
- package/build/graphql/reposContributedToDocument.js +59 -0
- package/build/index.d.ts +15 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +10 -0
- package/build/themes/index.d.ts +505 -0
- package/build/themes/index.d.ts.map +1 -0
- package/build/themes/index.js +505 -0
- package/build/translations.d.ts +1153 -0
- package/build/translations.d.ts.map +1 -0
- package/build/translations.js +1156 -0
- package/package.json +114 -0
- package/src/_emoji-name-map.d.ts +10 -0
- package/src/_github-username-regex.d.ts +4 -0
- package/src/api/api-result.ts +69 -0
- package/src/api/gist.ts +66 -0
- package/src/api/index.ts +16 -0
- package/src/api/params.ts +221 -0
- package/src/api/pin.ts +126 -0
- package/src/api/stats.ts +195 -0
- package/src/api/top-langs.ts +150 -0
- package/src/api/wakatime.ts +130 -0
- package/src/calculateRank.ts +91 -0
- package/src/cards/gist.ts +169 -0
- package/src/cards/index.ts +21 -0
- package/src/cards/options.ts +18 -0
- package/src/cards/repo.ts +376 -0
- package/src/cards/stats.ts +615 -0
- package/src/cards/top-languages.ts +1077 -0
- package/src/cards/wakatime.ts +501 -0
- package/src/common/Card.ts +364 -0
- package/src/common/I18n.ts +51 -0
- package/src/common/color.ts +296 -0
- package/src/common/config.ts +118 -0
- package/src/common/constants.ts +12 -0
- package/src/common/date.ts +34 -0
- package/src/common/error.ts +163 -0
- package/src/common/fmt.ts +86 -0
- package/src/common/html.ts +17 -0
- package/src/common/http.ts +98 -0
- package/src/common/icons.ts +80 -0
- package/src/common/languageColors.json +678 -0
- package/src/common/languageColors.ts +19 -0
- package/src/common/log.ts +12 -0
- package/src/common/ops.ts +145 -0
- package/src/common/render.ts +641 -0
- package/src/common/retryer.ts +111 -0
- package/src/fetchers/gist.ts +82 -0
- package/src/fetchers/index.ts +25 -0
- package/src/fetchers/repo.ts +140 -0
- package/src/fetchers/stats.ts +742 -0
- package/src/fetchers/top-languages.ts +134 -0
- package/src/fetchers/types.ts +136 -0
- package/src/fetchers/wakatime.ts +56 -0
- package/src/graphql/contributionsDocument.ts +44 -0
- package/src/graphql/generated/common.ts +26 -0
- package/src/graphql/generated/gist.ts +56 -0
- package/src/graphql/generated/repo.ts +79 -0
- package/src/graphql/generated/stats.ts +180 -0
- package/src/graphql/generated/top-languages.ts +66 -0
- package/src/graphql/graphqlDocument.ts +25 -0
- package/src/graphql/queries/gist.graphql +25 -0
- package/src/graphql/queries/repo.graphql +28 -0
- package/src/graphql/queries/stats.graphql +113 -0
- package/src/graphql/queries/top-languages.graphql +27 -0
- package/src/graphql/reposContributedToDocument.ts +83 -0
- package/src/index.ts +18 -0
- package/src/themes/index.ts +519 -0
- package/src/translations.ts +1169 -0
|
@@ -0,0 +1,134 @@
|
|
|
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';
|
|
9
|
+
import type {
|
|
10
|
+
TopLanguageFragment,
|
|
11
|
+
TopLanguagesRepositoryFragment,
|
|
12
|
+
} from '../graphql/generated/top-languages.js';
|
|
13
|
+
|
|
14
|
+
import type { Lang, TopLangData } from './types.js';
|
|
15
|
+
|
|
16
|
+
const fetcher = createGraphQLFetcher(TopLanguagesDocument, 'token');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Fetch top languages for a given username.
|
|
20
|
+
*
|
|
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
|
+
* @returns Top languages data.
|
|
29
|
+
*/
|
|
30
|
+
const fetchTopLanguages = async (
|
|
31
|
+
{
|
|
32
|
+
username,
|
|
33
|
+
exclude_repo = [],
|
|
34
|
+
size_weight = 1,
|
|
35
|
+
count_weight = 0,
|
|
36
|
+
ownerAffiliations = [],
|
|
37
|
+
}: {
|
|
38
|
+
username: string | undefined;
|
|
39
|
+
exclude_repo?: Array<string>;
|
|
40
|
+
size_weight?: number | undefined;
|
|
41
|
+
count_weight?: number | undefined;
|
|
42
|
+
ownerAffiliations?: Array<string>;
|
|
43
|
+
},
|
|
44
|
+
config: CardConfig,
|
|
45
|
+
): Promise<TopLangData> => {
|
|
46
|
+
if (!username) {
|
|
47
|
+
throw CardError.missingParam(['username']);
|
|
48
|
+
}
|
|
49
|
+
const affiliations = parseOwnerAffiliations(ownerAffiliations);
|
|
50
|
+
|
|
51
|
+
const res = await retryer(
|
|
52
|
+
fetcher,
|
|
53
|
+
{
|
|
54
|
+
login: username,
|
|
55
|
+
ownerAffiliations: affiliations,
|
|
56
|
+
},
|
|
57
|
+
config,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
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(
|
|
76
|
+
'Something went wrong while trying to retrieve the language data using the GraphQL API.',
|
|
77
|
+
{ code: 'upstream' },
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const repoToHide: Record<string, boolean> = {};
|
|
82
|
+
const allExcludedRepos = [...exclude_repo, ...config.excludeRepositories];
|
|
83
|
+
|
|
84
|
+
// populate repoToHide map for quick lookup while filtering out
|
|
85
|
+
allExcludedRepos.forEach((repoName) => {
|
|
86
|
+
repoToHide[repoName] = true;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// filter out repositories to be hidden
|
|
90
|
+
const repoNodes = (res.data.data.user?.repositories.nodes ?? []).filter(
|
|
91
|
+
(node): node is TopLanguagesRepositoryFragment => !!node && !repoToHide[node.name],
|
|
92
|
+
);
|
|
93
|
+
|
|
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) => {
|
|
97
|
+
const edges = (repo.languages?.edges ?? []).filter(
|
|
98
|
+
(edge): edge is TopLanguageFragment => !!edge,
|
|
99
|
+
);
|
|
100
|
+
return edges.length > 0 ? edges.concat(acc) : acc;
|
|
101
|
+
}, []);
|
|
102
|
+
|
|
103
|
+
// accumulate size and repo count per language
|
|
104
|
+
const languageMap: Record<string, Lang> = {};
|
|
105
|
+
let repoCount = 0;
|
|
106
|
+
for (const edge of languageEdges) {
|
|
107
|
+
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
|
+
languageMap[edge.node.name] = {
|
|
118
|
+
name: edge.node.name,
|
|
119
|
+
color: edge.node.color,
|
|
120
|
+
size: langSize,
|
|
121
|
+
count: repoCount,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// comparison index calculation
|
|
126
|
+
for (const lang of Object.values(languageMap)) {
|
|
127
|
+
lang.size = Math.pow(lang.size, size_weight) * Math.pow(lang.count, count_weight);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// return languages sorted by (weighted) size, descending
|
|
131
|
+
return Object.fromEntries(Object.entries(languageMap).sort(([, a], [, b]) => b.size - a.size));
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export { fetchTopLanguages };
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { RepoInfoFragment } from '../graphql/generated/repo.js';
|
|
2
|
+
|
|
3
|
+
export interface GistData {
|
|
4
|
+
name: string;
|
|
5
|
+
nameWithOwner: string;
|
|
6
|
+
description: string | null;
|
|
7
|
+
language: string | null;
|
|
8
|
+
starsCount: number;
|
|
9
|
+
forksCount: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* What the repo query returns, with `primaryLanguage` loosened:
|
|
14
|
+
* the schema says `name` is non-null, but the card falls back to defaults for callers passing untyped data.
|
|
15
|
+
*/
|
|
16
|
+
type RepoInfo = Omit<RepoInfoFragment, 'primaryLanguage'> & {
|
|
17
|
+
primaryLanguage: {
|
|
18
|
+
color: string | null;
|
|
19
|
+
id?: string;
|
|
20
|
+
name: string | null;
|
|
21
|
+
} | null;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export interface RepoUserStats {
|
|
25
|
+
// only present when the matching include_* flag is set (see fetchRepoUserStats)
|
|
26
|
+
totalPRsAuthored?: number;
|
|
27
|
+
totalPRsCommented?: number;
|
|
28
|
+
totalPRsReviewed?: number;
|
|
29
|
+
totalIssuesAuthored?: number;
|
|
30
|
+
totalIssuesCommented?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type RepositoryData = RepoInfo & RepoUserStats;
|
|
34
|
+
|
|
35
|
+
export interface StatsData {
|
|
36
|
+
name: string;
|
|
37
|
+
totalPRs: number;
|
|
38
|
+
totalPRsMerged: number;
|
|
39
|
+
mergedPRsPercentage: number;
|
|
40
|
+
totalReviews: number;
|
|
41
|
+
totalCommits: number;
|
|
42
|
+
totalIssues: number;
|
|
43
|
+
totalStars: number;
|
|
44
|
+
totalDiscussionsStarted: number;
|
|
45
|
+
totalDiscussionsAnswered: number;
|
|
46
|
+
contributedTo: number;
|
|
47
|
+
allTimeContributedTo: number;
|
|
48
|
+
totalPRsAuthored: number;
|
|
49
|
+
totalPRsCommented: number;
|
|
50
|
+
totalPRsReviewed: number;
|
|
51
|
+
totalIssuesAuthored: number;
|
|
52
|
+
totalIssuesCommented: number;
|
|
53
|
+
totalContributions: number;
|
|
54
|
+
rank: { level: string; percentile: number };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface Lang {
|
|
58
|
+
name: string;
|
|
59
|
+
// GitHub's GraphQL `Language.color` is nullable — the card falls back to a default.
|
|
60
|
+
color: string | null;
|
|
61
|
+
size: number;
|
|
62
|
+
/** Number of repositories the language appears in. */
|
|
63
|
+
count: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type TopLangData = Record<string, Lang>;
|
|
67
|
+
|
|
68
|
+
export interface WakaTimeData {
|
|
69
|
+
categories: Array<{
|
|
70
|
+
digital: string;
|
|
71
|
+
hours: number;
|
|
72
|
+
minutes: number;
|
|
73
|
+
name: string;
|
|
74
|
+
percent: number;
|
|
75
|
+
text: string;
|
|
76
|
+
total_seconds: number;
|
|
77
|
+
}>;
|
|
78
|
+
daily_average: number;
|
|
79
|
+
daily_average_including_other_language: number;
|
|
80
|
+
days_including_holidays: number;
|
|
81
|
+
days_minus_holidays: number;
|
|
82
|
+
editors: Array<{
|
|
83
|
+
digital: string;
|
|
84
|
+
hours: number;
|
|
85
|
+
minutes: number;
|
|
86
|
+
name: string;
|
|
87
|
+
percent: number;
|
|
88
|
+
text: string;
|
|
89
|
+
total_seconds: number;
|
|
90
|
+
}>;
|
|
91
|
+
holidays: number;
|
|
92
|
+
human_readable_daily_average: string;
|
|
93
|
+
human_readable_daily_average_including_other_language: string;
|
|
94
|
+
human_readable_total: string;
|
|
95
|
+
human_readable_total_including_other_language: string;
|
|
96
|
+
id: string;
|
|
97
|
+
is_already_updating: boolean;
|
|
98
|
+
is_coding_activity_visible: boolean;
|
|
99
|
+
is_including_today: boolean;
|
|
100
|
+
is_other_usage_visible: boolean;
|
|
101
|
+
is_stuck: boolean;
|
|
102
|
+
is_up_to_date: boolean;
|
|
103
|
+
languages: Array<{
|
|
104
|
+
digital: string;
|
|
105
|
+
hours: number;
|
|
106
|
+
minutes: number;
|
|
107
|
+
name: string;
|
|
108
|
+
percent: number;
|
|
109
|
+
text: string;
|
|
110
|
+
total_seconds: number;
|
|
111
|
+
}>;
|
|
112
|
+
operating_systems: Array<{
|
|
113
|
+
digital: string;
|
|
114
|
+
hours: number;
|
|
115
|
+
minutes: number;
|
|
116
|
+
name: string;
|
|
117
|
+
percent: number;
|
|
118
|
+
text: string;
|
|
119
|
+
total_seconds: number;
|
|
120
|
+
}>;
|
|
121
|
+
percent_calculated: number;
|
|
122
|
+
range: string;
|
|
123
|
+
status: string;
|
|
124
|
+
timeout: number;
|
|
125
|
+
total_seconds: number;
|
|
126
|
+
total_seconds_including_other_language: number;
|
|
127
|
+
user_id: string;
|
|
128
|
+
username: string;
|
|
129
|
+
writes_only: boolean;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface WakaTimeLang {
|
|
133
|
+
name: string;
|
|
134
|
+
text: string;
|
|
135
|
+
percent: number;
|
|
136
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
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';
|
|
4
|
+
|
|
5
|
+
import type { WakaTimeData } from './types.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* WakaTime data fetcher.
|
|
9
|
+
*
|
|
10
|
+
* WakaTime needs no GitHub token; the config is here for the transport it carries.
|
|
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
|
+
* @returns WakaTime data response.
|
|
17
|
+
*/
|
|
18
|
+
const fetchWakatimeStats = async (
|
|
19
|
+
{
|
|
20
|
+
username,
|
|
21
|
+
api_domain,
|
|
22
|
+
}: {
|
|
23
|
+
username: string | undefined;
|
|
24
|
+
api_domain?: string | undefined;
|
|
25
|
+
},
|
|
26
|
+
config: CardConfig,
|
|
27
|
+
): Promise<WakaTimeData> => {
|
|
28
|
+
if (!username) {
|
|
29
|
+
throw CardError.missingParam(['username']);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const domain = api_domain ? api_domain.replace(/\/$/gi, '') : 'wakatime.com';
|
|
33
|
+
const res = await httpRequest<{ data: WakaTimeData }>(
|
|
34
|
+
config.fetch,
|
|
35
|
+
`https://${domain}/api/v1/users/${username}/stats?is_including_today=true`,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
// Only a 404 says the profile does not exist.
|
|
39
|
+
// Any other failure is WakaTime being unavailable, which a retry can still answer,
|
|
40
|
+
// so it must not be reported as a permanent one a host would cache.
|
|
41
|
+
if (res.status === 404) {
|
|
42
|
+
throw new CardError(`Could not resolve to a User with the login of '${username}'`, {
|
|
43
|
+
code: 'not_found',
|
|
44
|
+
secondaryMessage: WAKATIME_USER_NOT_FOUND,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (res.status < 200 || res.status > 299) {
|
|
48
|
+
throw new CardError(`Could not fetch the WakaTime stats of '${username}'`, {
|
|
49
|
+
code: 'upstream',
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return res.data.data;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export { fetchWakatimeStats };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { getGitHubYearRange, toGitHubDateTime } from '../common/date.js';
|
|
2
|
+
|
|
3
|
+
import type { YearContributionsFragment } from './generated/stats.js';
|
|
4
|
+
import { graphqlDocument } from './graphqlDocument.js';
|
|
5
|
+
|
|
6
|
+
interface ContributionsQueryVariables {
|
|
7
|
+
login: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ContributionsQuery {
|
|
11
|
+
user: Record<`year_${number}`, YearContributionsFragment> | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Build the all-time contributions query, one aliased `contributionsCollection` field per year.
|
|
16
|
+
* The shape is only known at runtime.
|
|
17
|
+
*
|
|
18
|
+
* @param years Contribution years, one `year_<year>` alias each.
|
|
19
|
+
* @returns Document for `createGraphQLFetcher`.
|
|
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
|
+
|
|
29
|
+
// fragment must match queries/stats.graphql, which generates its type
|
|
30
|
+
return graphqlDocument<ContributionsQuery, ContributionsQueryVariables>(`
|
|
31
|
+
query userContributions($login: String!) {
|
|
32
|
+
user(login: $login) {
|
|
33
|
+
${yearFields}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
fragment YearContributions on ContributionsCollection {
|
|
37
|
+
contributionCalendar {
|
|
38
|
+
totalContributions
|
|
39
|
+
}
|
|
40
|
+
}`);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export { buildContributionsDocument };
|
|
44
|
+
export type { ContributionsQuery };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Generated file — see .github/CONTRIBUTING.md
|
|
2
|
+
|
|
3
|
+
export type Maybe<T> = T | null;
|
|
4
|
+
export type InputMaybe<T> = Maybe<T>;
|
|
5
|
+
/** All built-in and custom scalars, mapped to their actual values */
|
|
6
|
+
export type Scalars = {
|
|
7
|
+
ID: { input: string; output: string };
|
|
8
|
+
String: { input: string; output: string };
|
|
9
|
+
Boolean: { input: boolean; output: boolean };
|
|
10
|
+
Int: { input: number; output: number };
|
|
11
|
+
Float: { input: number; output: number };
|
|
12
|
+
/** An ISO-8601 encoded UTC date string. */
|
|
13
|
+
DateTime: { input: string; output: string };
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** The affiliation of a user to a repository */
|
|
17
|
+
export type RepositoryAffiliation =
|
|
18
|
+
/** Repositories that the user has been added to as a collaborator. */
|
|
19
|
+
| 'COLLABORATOR'
|
|
20
|
+
/**
|
|
21
|
+
* Repositories that the user has access to through being a member of an
|
|
22
|
+
* organization. This includes every repository on every team that the user is on.
|
|
23
|
+
*/
|
|
24
|
+
| 'ORGANIZATION_MEMBER'
|
|
25
|
+
/** Repositories that are owned by the authenticated user. */
|
|
26
|
+
| 'OWNER';
|
|
@@ -0,0 +1,56 @@
|
|
|
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.js';
|
|
6
|
+
export type GistFileInfoFragment = {
|
|
7
|
+
name: string | null;
|
|
8
|
+
size: number | null;
|
|
9
|
+
language: { name: string } | null;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type GistInfoQueryVariables = Exact<{
|
|
13
|
+
gistName: string;
|
|
14
|
+
}>;
|
|
15
|
+
|
|
16
|
+
export type GistInfoQuery = {
|
|
17
|
+
viewer: {
|
|
18
|
+
gist: {
|
|
19
|
+
description: string | null;
|
|
20
|
+
stargazerCount: number;
|
|
21
|
+
owner: { login: string } | { login: string } | null;
|
|
22
|
+
forks: { totalCount: number };
|
|
23
|
+
files: Array<{
|
|
24
|
+
name: string | null;
|
|
25
|
+
size: number | null;
|
|
26
|
+
language: { name: string } | null;
|
|
27
|
+
} | null> | null;
|
|
28
|
+
} | null;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const GistInfoDocument = graphqlDocument<GistInfoQuery, GistInfoQueryVariables>(`
|
|
33
|
+
query gistInfo($gistName: String!) {
|
|
34
|
+
viewer {
|
|
35
|
+
gist(name: $gistName) {
|
|
36
|
+
description
|
|
37
|
+
owner {
|
|
38
|
+
login
|
|
39
|
+
}
|
|
40
|
+
stargazerCount
|
|
41
|
+
forks {
|
|
42
|
+
totalCount
|
|
43
|
+
}
|
|
44
|
+
files {
|
|
45
|
+
...GistFileInfo
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
fragment GistFileInfo on GistFile {
|
|
51
|
+
name
|
|
52
|
+
language {
|
|
53
|
+
name
|
|
54
|
+
}
|
|
55
|
+
size
|
|
56
|
+
}`);
|
|
@@ -0,0 +1,79 @@
|
|
|
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.js';
|
|
6
|
+
export type RepoInfoFragment = {
|
|
7
|
+
name: string;
|
|
8
|
+
nameWithOwner: string;
|
|
9
|
+
isPrivate: boolean;
|
|
10
|
+
isArchived: boolean;
|
|
11
|
+
isTemplate: boolean;
|
|
12
|
+
stargazerCount: number;
|
|
13
|
+
description: string | null;
|
|
14
|
+
forkCount: number;
|
|
15
|
+
primaryLanguage: { color: string | null; id: string; name: string } | null;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type GetRepoQueryVariables = Exact<{
|
|
19
|
+
login: string;
|
|
20
|
+
repo: string;
|
|
21
|
+
}>;
|
|
22
|
+
|
|
23
|
+
export type GetRepoQuery = {
|
|
24
|
+
user: {
|
|
25
|
+
repository: {
|
|
26
|
+
name: string;
|
|
27
|
+
nameWithOwner: string;
|
|
28
|
+
isPrivate: boolean;
|
|
29
|
+
isArchived: boolean;
|
|
30
|
+
isTemplate: boolean;
|
|
31
|
+
stargazerCount: number;
|
|
32
|
+
description: string | null;
|
|
33
|
+
forkCount: number;
|
|
34
|
+
primaryLanguage: { color: string | null; id: string; name: string } | null;
|
|
35
|
+
} | null;
|
|
36
|
+
} | null;
|
|
37
|
+
organization: {
|
|
38
|
+
repository: {
|
|
39
|
+
name: string;
|
|
40
|
+
nameWithOwner: string;
|
|
41
|
+
isPrivate: boolean;
|
|
42
|
+
isArchived: boolean;
|
|
43
|
+
isTemplate: boolean;
|
|
44
|
+
stargazerCount: number;
|
|
45
|
+
description: string | null;
|
|
46
|
+
forkCount: number;
|
|
47
|
+
primaryLanguage: { color: string | null; id: string; name: string } | null;
|
|
48
|
+
} | null;
|
|
49
|
+
} | null;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const GetRepoDocument = graphqlDocument<GetRepoQuery, GetRepoQueryVariables>(`
|
|
53
|
+
query getRepo($login: String!, $repo: String!) {
|
|
54
|
+
user(login: $login) {
|
|
55
|
+
repository(name: $repo) {
|
|
56
|
+
...RepoInfo
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
organization(login: $login) {
|
|
60
|
+
repository(name: $repo) {
|
|
61
|
+
...RepoInfo
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
fragment RepoInfo on Repository {
|
|
66
|
+
name
|
|
67
|
+
nameWithOwner
|
|
68
|
+
isPrivate
|
|
69
|
+
isArchived
|
|
70
|
+
isTemplate
|
|
71
|
+
stargazerCount
|
|
72
|
+
description
|
|
73
|
+
primaryLanguage {
|
|
74
|
+
color
|
|
75
|
+
id
|
|
76
|
+
name
|
|
77
|
+
}
|
|
78
|
+
forkCount
|
|
79
|
+
}`);
|