@lizardbyte/contribkit 2025.315.185528 → 2025.316.4339

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/README.md CHANGED
@@ -11,18 +11,45 @@ This is a fork of [sponsorkit](https://github.com/antfu-collective/sponsorkit) t
11
11
 
12
12
  Supports:
13
13
 
14
- - [**GitHub Sponsors**](https://github.com/sponsors)
15
- - [**Patreon**](https://www.patreon.com/)
16
- - [**OpenCollective**](https://opencollective.com/)
17
- - [**Afdian**](https://afdian.com/)
18
- - [**Polar**](https://polar.sh/)
19
- - [**Liberapay**](https://liberapay.com/)
14
+ - Contributors:
15
+ - [**CrowdIn**](https://crowdin.com)
16
+ - [**GitHub**](https://github.com)
17
+ - [**Gitlab**](https://gitlab.com)
18
+ - Sponsors:
19
+ - [**GitHub Sponsors**](https://github.com/sponsors)
20
+ - [**Patreon**](https://www.patreon.com/)
21
+ - [**OpenCollective**](https://opencollective.com/)
22
+ - [**Afdian**](https://afdian.com/)
23
+ - [**Polar**](https://polar.sh/)
24
+ - [**Liberapay**](https://liberapay.com/)
20
25
 
21
26
  ## Usage
22
27
 
23
28
  Create `.env` file with:
24
29
 
25
30
  ```ini
31
+ ;; Contributors
32
+
33
+ ; CrowdInContributors provider.
34
+ CONTRIBKIT_CROWDIN_TOKEN=
35
+ CONTRIBKIT_CROWDIN_PROJECT_ID=
36
+ CONTRIBKIT_CROWDIN_MIN_TRANSLATIONS=1
37
+
38
+ ; GitHubContributors provider.
39
+ ; Token requires the `public_repo` and `read:user` scopes.
40
+ CONTRIBKIT_GITHUB_CONTRIBUTORS_TOKEN=
41
+ CONTRIBKIT_GITHUB_CONTRIBUTORS_LOGIN=
42
+ CONTRIBKIT_GITHUB_CONTRIBUTORS_MIN=1
43
+ CONTRIBKIT_GITHUB_CONTRIBUTORS_REPO=
44
+
45
+ ; GitlabContributors provider.
46
+ ; Token requires the `read_api` and `read_user` scopes.
47
+ CONTRIBKIT_GITLAB_CONTRIBUTORS_TOKEN=
48
+ CONTRIBKIT_GITLAB_CONTRIBUTORS_MIN=1
49
+ CONTRIBKIT_GITLAB_CONTRIBUTORS_REPO_ID=
50
+
51
+ ;; Sponsors
52
+
26
53
  ; GitHub provider.
27
54
  ; Token requires the `read:user` and `read:org` scopes.
28
55
  CONTRIBKIT_GITHUB_TOKEN=
@@ -64,6 +91,11 @@ CONTRIBKIT_LIBERAPAY_LOGIN=
64
91
 
65
92
  > Only one provider is required to be configured.
66
93
 
94
+ > ![NOTE]
95
+ > The contributor providers are intended to be separated from each other, unlike the sponsor providers.
96
+ > This will require different env variables to be set for each provider, and to be created from separate
97
+ > commands.
98
+
67
99
  Run:
68
100
 
69
101
  ```base
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import cac from 'cac';
2
- import { S as SvgComposer, i as generateBadge, p as partitionTiers, t as tierPresets, v as version, l as loadConfig, k as resolveProviders, j as guessProviders, r as resolveAvatars, q as outputFormats, s as svgToPng, g as svgToWebp } from './shared/contribkit.QISHqB4U.mjs';
2
+ import { S as SvgComposer, i as generateBadge, p as partitionTiers, t as tierPresets, v as version, l as loadConfig, k as resolveProviders, j as guessProviders, r as resolveAvatars, q as outputFormats, s as svgToPng, g as svgToWebp } from './shared/contribkit.CL3hIz1T.mjs';
3
3
  import fs from 'node:fs';
4
4
  import fsp from 'node:fs/promises';
5
5
  import { resolve, dirname, relative, join } from 'node:path';
@@ -12,6 +12,7 @@ import 'dotenv';
12
12
  import 'ofetch';
13
13
  import 'sharp';
14
14
  import 'node:crypto';
15
+ import '@crowdin/crowdin-api-client';
15
16
 
16
17
  function notNullish(v) {
17
18
  return v != null;
package/dist/index.d.mts CHANGED
@@ -83,7 +83,7 @@ interface Sponsorship {
83
83
  }
84
84
  declare const outputFormats: readonly ["svg", "png", "webp", "json"];
85
85
  type OutputFormat = typeof outputFormats[number];
86
- type ProviderName = 'github' | 'patreon' | 'opencollective' | 'afdian' | 'polar' | 'liberapay';
86
+ type ProviderName = 'github' | 'patreon' | 'opencollective' | 'afdian' | 'polar' | 'liberapay' | 'githubContributors' | 'gitlabContributors' | 'crowdinContributors';
87
87
  type GitHubAccountType = 'user' | 'organization';
88
88
  interface ProvidersConfig {
89
89
  github?: {
@@ -208,6 +208,76 @@ interface ProvidersConfig {
208
208
  */
209
209
  login?: string;
210
210
  };
211
+ githubContributors?: {
212
+ /**
213
+ * User id of your GitHub account.
214
+ *
215
+ * Will read from `CONTRIBKIT_GITHUB_CONTRIBUTORS_LOGIN` environment variable if not set.
216
+ */
217
+ login?: string;
218
+ /**
219
+ * GitHub Token that have access to your sponsorships.
220
+ *
221
+ * Will read from `CONTRIBKIT_GITHUB_CONTRIBUTORS_TOKEN` environment variable if not set.
222
+ *
223
+ * @deprecated It's not recommended set this value directly, pass from env or use `.env` file.
224
+ */
225
+ token?: string;
226
+ /**
227
+ * The minimum number of contributions to be considered a sponsor.
228
+ *
229
+ * @default 1
230
+ */
231
+ minContributions?: number;
232
+ /**
233
+ * The repository to fetch contributors from.
234
+ *
235
+ * Will read from `CONTRIBKIT_GITHUB_CONTRIBUTORS_REPO` environment variable if not set.
236
+ */
237
+ repo?: string;
238
+ };
239
+ gitlabContributors?: {
240
+ /**
241
+ * Gitlab Token that have access contributors.
242
+ *
243
+ * Will read from `CONTRIBKIT_GITLAB_CONTRIBUTORS_TOKEN` environment variable if not set.
244
+ *
245
+ * @deprecated It's not recommended set this value directly, pass from env or use `.env` file.
246
+ */
247
+ token?: string;
248
+ /**
249
+ * The minimum number of contributions to be considered a sponsor.
250
+ *
251
+ * @default 1
252
+ */
253
+ minContributions?: number;
254
+ /**
255
+ * The repository ID to fetch contributors from.
256
+ *
257
+ * Will read from `CONTRIBKIT_GITLAB_CONTRIBUTORS_REPO_ID` environment variable if not set.
258
+ */
259
+ repoId?: number;
260
+ };
261
+ crowdinContributors?: {
262
+ /**
263
+ * The Crowdin API token.
264
+ *
265
+ * Will read from `CONTRIBKIT_CROWDIN_TOKEN` environment variable if not set.
266
+ */
267
+ token?: string;
268
+ /**
269
+ * The project id on Crowdin.
270
+ *
271
+ * Will read from `CONTRIBKIT_CROWDIN_PROJECT_ID` environment variable if not set.
272
+ */
273
+ projectId?: number;
274
+ /**
275
+ * The minimum number of translations to be considered a sponsor.
276
+ *
277
+ * @default 100
278
+ */
279
+ minTranslations?: number;
280
+ };
211
281
  }
212
282
  interface ContribkitRenderOptions {
213
283
  /**
@@ -486,6 +556,9 @@ declare const ProvidersMap: {
486
556
  afdian: Provider;
487
557
  polar: Provider;
488
558
  liberapay: Provider;
559
+ githubContributors: Provider;
560
+ gitlabContributors: Provider;
561
+ crowdinContributors: Provider;
489
562
  };
490
563
  declare function guessProviders(config: ContribkitConfig): ProviderName[];
491
564
  declare function resolveProviders(names: (ProviderName | Provider)[]): Provider[];
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { F as FALLBACK_AVATAR, G as GitHubProvider, P as ProvidersMap, S as SvgComposer, c as defaultConfig, b as defaultInlineCSS, a as defaultTiers, d as defineConfig, n as fetchGitHubSponsors, m as fetchSponsors, h as genSvgImage, i as generateBadge, j as guessProviders, l as loadConfig, o as makeQuery, q as outputFormats, p as partitionTiers, e as presets, f as resizeImage, r as resolveAvatars, k as resolveProviders, s as svgToPng, g as svgToWebp, t as tierPresets } from './shared/contribkit.QISHqB4U.mjs';
1
+ export { F as FALLBACK_AVATAR, G as GitHubProvider, P as ProvidersMap, S as SvgComposer, c as defaultConfig, b as defaultInlineCSS, a as defaultTiers, d as defineConfig, n as fetchGitHubSponsors, m as fetchSponsors, h as genSvgImage, i as generateBadge, j as guessProviders, l as loadConfig, o as makeQuery, q as outputFormats, p as partitionTiers, e as presets, f as resizeImage, r as resolveAvatars, k as resolveProviders, s as svgToPng, g as svgToWebp, t as tierPresets } from './shared/contribkit.CL3hIz1T.mjs';
2
2
  import 'unconfig';
3
3
  import 'node:process';
4
4
  import 'dotenv';
@@ -7,3 +7,4 @@ import 'consola';
7
7
  import 'ofetch';
8
8
  import 'sharp';
9
9
  import 'node:crypto';
10
+ import '@crowdin/crowdin-api-client';
@@ -6,6 +6,7 @@ import { consola } from 'consola';
6
6
  import { $fetch, ofetch } from 'ofetch';
7
7
  import sharp from 'sharp';
8
8
  import { createHash } from 'node:crypto';
9
+ import { ProjectsGroups, Reports } from '@crowdin/crowdin-api-client';
9
10
 
10
11
  const none = {
11
12
  avatar: {
@@ -179,12 +180,28 @@ function loadEnv() {
179
180
  liberapay: {
180
181
  login: process.env.CONTRIBKIT_LIBERAPAY_LOGIN || process.env.LIBERAPAY_LOGIN
181
182
  },
182
- outputDir: process.env.CONTRIBKIT_DIR
183
+ outputDir: process.env.CONTRIBKIT_DIR,
184
+ githubContributors: {
185
+ login: process.env.CONTRIBKIT_GITHUB_CONTRIBUTORS_LOGIN,
186
+ token: process.env.CONTRIBKIT_GITHUB_CONTRIBUTORS_TOKEN,
187
+ minContributions: Number(process.env.CONTRIBKIT_GITHUB_CONTRIBUTORS_MIN) || 1,
188
+ repo: process.env.CONTRIBKIT_GITHUB_CONTRIBUTORS_REPO
189
+ },
190
+ gitlabContributors: {
191
+ token: process.env.CONTRIBKIT_GITLAB_CONTRIBUTORS_TOKEN,
192
+ minContributions: Number(process.env.CONTRIBKIT_GITLAB_CONTRIBUTORS_MIN) || 1,
193
+ repoId: Number(process.env.CONTRIBKIT_GITLAB_CONTRIBUTORS_REPO_ID)
194
+ },
195
+ crowdinContributors: {
196
+ token: process.env.CONTRIBKIT_CROWDIN_TOKEN,
197
+ projectId: Number(process.env.CONTRIBKIT_CROWDIN_PROJECT_ID),
198
+ minTranslations: Number(process.env.CONTRIBKIT_CROWDIN_MIN_TRANSLATIONS) || 1
199
+ }
183
200
  };
184
201
  return JSON.parse(JSON.stringify(config));
185
202
  }
186
203
 
187
- const version = "2025.315.185528";
204
+ const version = "2025.316.4339";
188
205
 
189
206
  async function fetchImage(url) {
190
207
  const arrayBuffer = await $fetch(url, {
@@ -265,10 +282,16 @@ async function loadConfig(inlineConfig = {}) {
265
282
  const { config = {} } = await loadConfig$1({
266
283
  sources: [
267
284
  {
268
- files: "sponsorkit.config"
285
+ files: "contrib.config"
269
286
  },
270
287
  {
271
288
  files: "contribkit.config"
289
+ },
290
+ {
291
+ files: "sponsor.config"
292
+ },
293
+ {
294
+ files: "sponsorkit.config"
272
295
  }
273
296
  ],
274
297
  merge: true
@@ -503,6 +526,65 @@ function md5(token, params, ts, userId) {
503
526
  return createHash("md5").update(`${token}params${params}ts${ts}user_id${userId}`).digest("hex");
504
527
  }
505
528
 
529
+ const CrowdinContributorsProvider = {
530
+ name: "crowdinContributors",
531
+ fetchSponsors(config) {
532
+ return fetchCrowdinContributors(
533
+ config.crowdinContributors?.token || config.token,
534
+ config.crowdinContributors?.projectId || 0,
535
+ config.crowdinContributors?.minTranslations || 1
536
+ );
537
+ }
538
+ };
539
+ async function fetchCrowdinContributors(token, projectId, minTranslations = 1) {
540
+ if (!token)
541
+ throw new Error("Crowdin token is required");
542
+ if (!projectId)
543
+ throw new Error("Crowdin project ID is required");
544
+ const credentials = {
545
+ token
546
+ };
547
+ const projectsGroups = new ProjectsGroups(credentials);
548
+ const project = await projectsGroups.getProject(projectId);
549
+ const reports = new Reports(credentials);
550
+ const dateTo = (/* @__PURE__ */ new Date()).toISOString();
551
+ const dateFrom = project.data.createdAt;
552
+ const createReportRequestBody = {
553
+ name: "top-members",
554
+ schema: {
555
+ unit: "words",
556
+ format: "json",
557
+ dateFrom,
558
+ dateTo
559
+ }
560
+ };
561
+ const createReport = await reports.generateReport(projectId, createReportRequestBody);
562
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
563
+ const report = await reports.downloadReport(projectId, createReport.data.identifier);
564
+ const reportRaw = await fetch(report.data.url);
565
+ const reportData = await reportRaw.json();
566
+ const contributors = reportData.data.filter((entry) => entry.translated > minTranslations).map((entry) => ({
567
+ member: entry.user,
568
+ translations: entry.translated
569
+ }));
570
+ return contributors.filter(Boolean).map(({ member, translations }) => ({
571
+ sponsor: {
572
+ type: "User",
573
+ login: member.username,
574
+ name: member.username,
575
+ // fullName is also available
576
+ avatarUrl: member.avatarUrl,
577
+ linkUrl: `https://crowdin.com/profile/${member.username}`
578
+ },
579
+ isOneTime: false,
580
+ monthlyDollars: translations,
581
+ privacyLevel: "PUBLIC",
582
+ tierName: "Translator",
583
+ createdAt: member.joinedAt,
584
+ provider: "crowdinContributors"
585
+ }));
586
+ }
587
+
506
588
  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
507
589
  const DATA_URL_DEFAULT_MIME_TYPE = 'text/plain';
508
590
  const DATA_URL_DEFAULT_CHARSET = 'us-ascii';
@@ -939,6 +1021,149 @@ function makeQuery(login, type, activeOnly = true, cursor) {
939
1021
  }`;
940
1022
  }
941
1023
 
1024
+ const GitHubContributorsProvider = {
1025
+ name: "githubContributors",
1026
+ fetchSponsors(config) {
1027
+ if (!config.githubContributors?.repo)
1028
+ throw new Error("GitHub repository is required");
1029
+ return fetchGitHubContributors(
1030
+ config.githubContributors?.token || config.token,
1031
+ config.githubContributors?.login || config.login,
1032
+ config.githubContributors.repo,
1033
+ config.githubContributors?.minContributions
1034
+ );
1035
+ }
1036
+ };
1037
+ async function fetchGitHubContributors(token, login, repo, minContributions = 1) {
1038
+ if (!token)
1039
+ throw new Error("GitHub token is required");
1040
+ if (!login)
1041
+ throw new Error("GitHub login is required");
1042
+ if (!repo)
1043
+ throw new Error("GitHub repository is required");
1044
+ const allContributors = [];
1045
+ let page = 1;
1046
+ let hasNextPage = true;
1047
+ while (hasNextPage) {
1048
+ const response = await $fetch(
1049
+ `https://api.github.com/repos/${login}/${repo}/contributors`,
1050
+ {
1051
+ query: {
1052
+ page: String(page),
1053
+ per_page: "100"
1054
+ },
1055
+ headers: {
1056
+ Authorization: `bearer ${token}`,
1057
+ Accept: "application/vnd.github.v3+json"
1058
+ }
1059
+ }
1060
+ );
1061
+ if (!response || !response.length)
1062
+ break;
1063
+ allContributors.push(...response);
1064
+ hasNextPage = response.length === 100;
1065
+ page++;
1066
+ }
1067
+ return allContributors.filter(
1068
+ (contributor) => contributor.type === "User" && contributor.contributions >= minContributions
1069
+ ).map((contributor) => ({
1070
+ sponsor: {
1071
+ type: "User",
1072
+ login: contributor.login,
1073
+ name: contributor.login,
1074
+ avatarUrl: contributor.avatar_url,
1075
+ linkUrl: contributor.url
1076
+ },
1077
+ isOneTime: false,
1078
+ monthlyDollars: contributor.contributions,
1079
+ privacyLevel: "PUBLIC",
1080
+ tierName: "Contributor",
1081
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1082
+ provider: "githubContributors"
1083
+ }));
1084
+ }
1085
+
1086
+ const GitlabContributorsProvider = {
1087
+ name: "gitlabContributors",
1088
+ fetchSponsors(config) {
1089
+ if (!config.gitlabContributors?.repoId)
1090
+ throw new Error("Gitlab repoId is required");
1091
+ return fetchGitlabContributors(
1092
+ config.gitlabContributors?.token || config.token,
1093
+ config.gitlabContributors.repoId,
1094
+ config.gitlabContributors?.minContributions
1095
+ );
1096
+ }
1097
+ };
1098
+ async function fetchGitlabContributors(token, repoId, minContributions = 1) {
1099
+ if (!token)
1100
+ throw new Error("Gitlab token is required");
1101
+ if (!repoId)
1102
+ throw new Error("Gitlab repoId is required");
1103
+ const allContributors = [];
1104
+ let page = 1;
1105
+ let hasNextPage = true;
1106
+ while (hasNextPage) {
1107
+ const response = await $fetch(
1108
+ `https://gitlab.com/api/v4/projects/${repoId}/repository/contributors`,
1109
+ {
1110
+ query: {
1111
+ page: String(page),
1112
+ per_page: "100",
1113
+ sort: "desc"
1114
+ },
1115
+ headers: {
1116
+ "PRIVATE-TOKEN": token,
1117
+ "Content-Type": "application/json"
1118
+ }
1119
+ }
1120
+ );
1121
+ if (!response || !response.length)
1122
+ break;
1123
+ allContributors.push(...response);
1124
+ hasNextPage = response.length === 100;
1125
+ page++;
1126
+ }
1127
+ const sponsorships = [];
1128
+ for (const contributor of allContributors) {
1129
+ if (contributor.commits < minContributions)
1130
+ continue;
1131
+ try {
1132
+ const userDetails = await $fetch("https://gitlab.com/api/v4/users", {
1133
+ query: {
1134
+ search: contributor.email
1135
+ },
1136
+ headers: {
1137
+ "PRIVATE-TOKEN": token,
1138
+ "Content-Type": "application/json"
1139
+ }
1140
+ });
1141
+ if (userDetails && userDetails.length > 0) {
1142
+ const user = userDetails[0];
1143
+ sponsorships.push({
1144
+ sponsor: {
1145
+ type: "User",
1146
+ login: user.username,
1147
+ name: user.username,
1148
+ // user.name is also available
1149
+ avatarUrl: user.avatar_url,
1150
+ linkUrl: user.web_url
1151
+ },
1152
+ isOneTime: false,
1153
+ monthlyDollars: contributor.commits,
1154
+ privacyLevel: "PUBLIC",
1155
+ tierName: "Contributor",
1156
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1157
+ provider: "gitlabContributors"
1158
+ });
1159
+ }
1160
+ } catch (error) {
1161
+ console.warn(`Failed to fetch user details for ${contributor.email}:`, error);
1162
+ }
1163
+ }
1164
+ return sponsorships;
1165
+ }
1166
+
942
1167
  const LiberapayProvider = {
943
1168
  name: "liberapay",
944
1169
  fetchSponsors(config) {
@@ -1409,7 +1634,10 @@ const ProvidersMap = {
1409
1634
  opencollective: OpenCollectiveProvider,
1410
1635
  afdian: AfdianProvider,
1411
1636
  polar: PolarProvider,
1412
- liberapay: LiberapayProvider
1637
+ liberapay: LiberapayProvider,
1638
+ githubContributors: GitHubContributorsProvider,
1639
+ gitlabContributors: GitlabContributorsProvider,
1640
+ crowdinContributors: CrowdinContributorsProvider
1413
1641
  };
1414
1642
  function guessProviders(config) {
1415
1643
  const items = [];
@@ -1425,6 +1653,12 @@ function guessProviders(config) {
1425
1653
  items.push("polar");
1426
1654
  if (config.liberapay && config.liberapay.login)
1427
1655
  items.push("liberapay");
1656
+ if (config.githubContributors?.login && config.githubContributors?.token)
1657
+ items.push("githubContributors");
1658
+ if (config.gitlabContributors?.token && config.gitlabContributors?.repoId)
1659
+ items.push("gitlabContributors");
1660
+ if (config.crowdinContributors?.token && config.crowdinContributors?.projectId)
1661
+ items.push("crowdinContributors");
1428
1662
  if (!items.length)
1429
1663
  items.push("github");
1430
1664
  return items;
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/LizardByte/contribkit.git"
7
7
  },
8
- "version": "2025.315.185528",
8
+ "version": "2025.316.4339",
9
9
  "description": "Toolkit for generating contributor images",
10
10
  "license": "MIT",
11
11
  "funding": "https://app.lizardbyte.dev",
@@ -46,6 +46,7 @@
46
46
  "release": "bumpp && pnpm publish"
47
47
  },
48
48
  "dependencies": {
49
+ "@crowdin/crowdin-api-client": "^1.41.2",
49
50
  "ansis": "^3.17.0",
50
51
  "cac": "^6.7.14",
51
52
  "consola": "^3.4.0",