Skip to content

Commit 4123ef1

Browse files
fix!: centralize env credentials and remove inline tokens
Introduce src/configs/credentials.ts to load credentials from environment (dotenv) and expose getCredentials(). Modify loadEnv() to return both parsed config and credentials, and propagate credentials into loadConfig(). Update providers to read tokens via getCredentials() and accept optional tokens, removing direct token fallbacks. Adjust ProvidersMap.guessProviders to check credentials. Clean up types: remove deprecated token fields from provider configs to encourage using env/credentials. Update README to mention CONTRIBKIT_* environment variables. Note: this is a breaking change for code that previously set provider tokens directly in the config; tokens should now be provided via env vars or the new credentials API.
1 parent cc193a6 commit 4123ef1

15 files changed

Lines changed: 136 additions & 135 deletions

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,13 +225,14 @@ import { fetchSponsors } from '@lizardbyte/contribkit'
225225

226226
const sponsors = await fetchSponsors({
227227
github: {
228-
token,
229228
login,
230229
},
231230
// ...
232231
})
233232
```
234233

234+
Set credentials with environment variables such as `CONTRIBKIT_GITHUB_TOKEN`.
235+
235236
Check the type definition or source code for more utils available.
236237

237238
### Renderers

src/configs/credentials.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import type { ContribkitConfig } from '../types'
2+
import process from 'node:process'
3+
import dotenv from 'dotenv'
4+
5+
export interface EnvCredentials {
6+
github?: {
7+
token?: string
8+
}
9+
patreon?: {
10+
token?: string
11+
}
12+
opencollective?: {
13+
key?: string
14+
}
15+
afdian?: {
16+
token?: string
17+
}
18+
polar?: {
19+
token?: string
20+
}
21+
githubContributors?: {
22+
token?: string
23+
}
24+
gitlabContributors?: {
25+
token?: string
26+
}
27+
githubContributions?: {
28+
token?: string
29+
}
30+
}
31+
32+
interface ConfigWithCredentials extends ContribkitConfig {
33+
credentials?: EnvCredentials
34+
}
35+
36+
export function loadEnvCredentials(): EnvCredentials {
37+
dotenv.config({ quiet: true })
38+
39+
return JSON.parse(JSON.stringify({
40+
github: {
41+
token: process.env.CONTRIBKIT_GITHUB_TOKEN || process.env.GITHUB_TOKEN,
42+
},
43+
patreon: {
44+
token: process.env.CONTRIBKIT_PATREON_TOKEN || process.env.PATREON_TOKEN,
45+
},
46+
opencollective: {
47+
key: process.env.CONTRIBKIT_OPENCOLLECTIVE_KEY || process.env.OPENCOLLECTIVE_KEY,
48+
},
49+
afdian: {
50+
token: process.env.CONTRIBKIT_AFDIAN_TOKEN || process.env.AFDIAN_TOKEN,
51+
},
52+
polar: {
53+
token: process.env.CONTRIBKIT_POLAR_TOKEN || process.env.POLAR_TOKEN,
54+
},
55+
githubContributors: {
56+
token: process.env.CONTRIBKIT_GITHUB_CONTRIBUTORS_TOKEN,
57+
},
58+
gitlabContributors: {
59+
token: process.env.CONTRIBKIT_GITLAB_CONTRIBUTORS_TOKEN,
60+
},
61+
githubContributions: {
62+
token: process.env.CONTRIBKIT_GITHUB_CONTRIBUTIONS_TOKEN,
63+
},
64+
}))
65+
}
66+
67+
export function getCredentials(config: ContribkitConfig): EnvCredentials {
68+
return (config as ConfigWithCredentials).credentials ?? loadEnvCredentials()
69+
}

src/configs/env.ts

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,33 @@
11
import type { ContribkitConfig, GitHubAccountType } from '../types'
22
import process from 'node:process'
33
import dotenv from 'dotenv'
4+
import { loadEnvCredentials } from './credentials'
45

5-
function getDeprecatedEnv(name: string, replacement: string) {
6-
const value = process.env[name]
7-
if (value)
8-
console.warn(`[contribkit] env.${name} is deprecated, use env.${replacement} instead`)
9-
return value
6+
export interface EnvConfig {
7+
config: Partial<ContribkitConfig>
8+
credentials: ReturnType<typeof loadEnvCredentials>
109
}
1110

12-
export function loadEnv(): Partial<ContribkitConfig> {
11+
export function loadEnv(): EnvConfig {
1312
dotenv.config({ quiet: true })
1413

1514
const config: Partial<ContribkitConfig> = {
1615
mode: process.env.CONTRIBKIT_MODE as ContribkitConfig['mode'] | undefined,
1716
github: {
1817
login: process.env.CONTRIBKIT_GITHUB_LOGIN || process.env.GITHUB_LOGIN,
19-
token: process.env.CONTRIBKIT_GITHUB_TOKEN || process.env.GITHUB_TOKEN,
2018
type: (process.env.CONTRIBKIT_GITHUB_TYPE || process.env.GITHUB_TYPE) as GitHubAccountType | undefined,
2119
},
22-
patreon: {
23-
token: process.env.CONTRIBKIT_PATREON_TOKEN || process.env.PATREON_TOKEN,
24-
},
2520
opencollective: {
26-
key: process.env.CONTRIBKIT_OPENCOLLECTIVE_KEY || process.env.OPENCOLLECTIVE_KEY,
2721
id: process.env.CONTRIBKIT_OPENCOLLECTIVE_ID || process.env.OPENCOLLECTIVE_ID,
2822
slug: process.env.CONTRIBKIT_OPENCOLLECTIVE_SLUG || process.env.OPENCOLLECTIVE_SLUG,
2923
githubHandle: process.env.CONTRIBKIT_OPENCOLLECTIVE_GH_HANDLE || process.env.OPENCOLLECTIVE_GH_HANDLE,
3024
type: process.env.CONTRIBKIT_OPENCOLLECTIVE_TYPE || process.env.OPENCOLLECTIVE_TYPE,
3125
},
3226
afdian: {
3327
userId: process.env.CONTRIBKIT_AFDIAN_USER_ID || process.env.AFDIAN_USER_ID,
34-
token: process.env.CONTRIBKIT_AFDIAN_TOKEN || process.env.AFDIAN_TOKEN,
3528
exchangeRate: Number.parseFloat(process.env.CONTRIBKIT_AFDIAN_EXCHANGE_RATE || process.env.AFDIAN_EXCHANGE_RATE || '0') || undefined,
3629
},
3730
polar: {
38-
token: process.env.CONTRIBKIT_POLAR_TOKEN || process.env.POLAR_TOKEN,
3931
organization: process.env.CONTRIBKIT_POLAR_ORGANIZATION || process.env.POLAR_ORGANIZATION,
4032
},
4133
liberapay: {
@@ -44,12 +36,10 @@ export function loadEnv(): Partial<ContribkitConfig> {
4436
outputDir: process.env.CONTRIBKIT_DIR,
4537
githubContributors: {
4638
login: process.env.CONTRIBKIT_GITHUB_CONTRIBUTORS_LOGIN,
47-
token: process.env.CONTRIBKIT_GITHUB_CONTRIBUTORS_TOKEN,
4839
minContributions: Number(process.env.CONTRIBKIT_GITHUB_CONTRIBUTORS_MIN) || 1,
4940
repo: process.env.CONTRIBKIT_GITHUB_CONTRIBUTORS_REPO,
5041
},
5142
gitlabContributors: {
52-
token: process.env.CONTRIBKIT_GITLAB_CONTRIBUTORS_TOKEN,
5343
minContributions: Number(process.env.CONTRIBKIT_GITLAB_CONTRIBUTORS_MIN) || 1,
5444
repoId: Number(process.env.CONTRIBKIT_GITLAB_CONTRIBUTORS_REPO_ID),
5545
},
@@ -60,12 +50,14 @@ export function loadEnv(): Partial<ContribkitConfig> {
6050
},
6151
githubContributions: {
6252
login: process.env.CONTRIBKIT_GITHUB_CONTRIBUTIONS_LOGIN,
63-
token: process.env.CONTRIBKIT_GITHUB_CONTRIBUTIONS_TOKEN,
6453
maxContributions: Number(process.env.CONTRIBKIT_GITHUB_CONTRIBUTIONS_MAX) || undefined,
6554
logarithmicScaling: process.env.CONTRIBKIT_GITHUB_CONTRIBUTIONS_LOGARITHMIC === 'true',
6655
},
6756
}
6857

6958
// remove undefined keys
70-
return JSON.parse(JSON.stringify(config))
59+
return {
60+
config: JSON.parse(JSON.stringify(config)),
61+
credentials: loadEnvCredentials(),
62+
}
7163
}

src/configs/index.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export function defineConfig(config: ContribkitConfig): ContribkitConfig {
1414

1515
export async function loadConfig(inlineConfig: ContribkitConfig = {}): Promise<Required<ContribkitConfig>> {
1616
const env = loadEnv()
17+
const envConfig = env.config
1718

1819
const { config = {} } = await _loadConfig<ContribkitConfig>({
1920
sources: [
@@ -39,35 +40,36 @@ export async function loadConfig(inlineConfig: ContribkitConfig = {}): Promise<R
3940
fallbackAvatar: FALLBACK_AVATAR,
4041
includePastSponsors: hasNegativeTier,
4142
...defaultConfig,
42-
...env,
43+
...envConfig,
4344
...config,
4445
...inlineConfig,
4546
github: {
46-
...env.github,
47+
...envConfig.github,
4748
...config.github,
4849
...inlineConfig.github,
4950
},
5051
patreon: {
51-
...env.patreon,
52+
...envConfig.patreon,
5253
...config.patreon,
5354
...inlineConfig.patreon,
5455
},
5556
opencollective: {
56-
...env.opencollective,
57+
...envConfig.opencollective,
5758
...config.opencollective,
5859
...inlineConfig.opencollective,
5960
},
6061
afdian: {
61-
...env.afdian,
62+
...envConfig.afdian,
6263
...config.afdian,
6364
...inlineConfig.afdian,
6465
},
66+
credentials: env.credentials,
6567
} as Required<ContribkitConfig>
6668

6769
if (!['sponsors', 'sponsees'].includes(resolved.mode))
6870
throw new Error(`Invalid mode: ${resolved.mode}. Expected "sponsors" or "sponsees".`)
6971

70-
resolved.name = inlineConfig.name || config.name || env.name || resolved.mode
72+
resolved.name = inlineConfig.name || config.name || envConfig.name || resolved.mode
7173

7274
return resolved
7375
}

src/providers/afdian.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ContribkitConfig, Provider, Sponsorship } from '../types'
22
import { createHash } from 'node:crypto'
33
import { $fetch } from 'ofetch'
4+
import { getCredentials } from '../configs/credentials'
45

56
// afdian api docs https://afdian.net/p/9c65d9cc617011ed81c352540025c377
67

@@ -12,14 +13,13 @@ export const AfdianProvider: Provider = {
1213
return Promise.resolve([])
1314
}
1415

15-
return fetchAfdianSponsors(config.afdian)
16+
return fetchAfdianSponsors(config.afdian, getCredentials(config).afdian?.token)
1617
},
1718
}
1819

19-
export async function fetchAfdianSponsors(options: ContribkitConfig['afdian'] = {}): Promise<Sponsorship[]> {
20+
export async function fetchAfdianSponsors(options: ContribkitConfig['afdian'] = {}, token = getCredentials({}).afdian?.token): Promise<Sponsorship[]> {
2021
const {
2122
userId,
22-
token,
2323
exchangeRate = 6.5,
2424
includePurchases = true,
2525
purchaseEffectivity = 30,

src/providers/crowdinContributors.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ export const CrowdinContributorsProvider: Provider = {
1414
name: 'crowdinContributors',
1515
fetchSponsors(config) {
1616
return fetchCrowdinContributors(
17-
config.crowdinContributors?.token || config.token!,
17+
config.crowdinContributors?.token,
1818
config.crowdinContributors?.projectId || 0,
1919
config.crowdinContributors?.minTranslations || 1,
2020
)
2121
},
2222
}
2323

2424
export async function fetchCrowdinContributors(
25-
token: string,
25+
token: string | undefined,
2626
projectId: number,
2727
minTranslations = 1,
2828
): Promise<Sponsorship[]> {

src/providers/github.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ContribkitConfig, GitHubAccountType, Provider, Sponsorship, Tier } from '../types'
22
import { $fetch } from 'ofetch'
3+
import { getCredentials } from '../configs/credentials'
34
import { normalizeUrl } from '../utils'
45

56
function getMonthDifference(startDate: Date, endDate: Date) {
@@ -42,24 +43,24 @@ export const GitHubProvider: Provider = {
4243
fetchSponsors(config) {
4344
if (config.mode === 'sponsees') {
4445
return fetchGitHubSponsoringAsSponsorships(
45-
config.github?.token || config.token!,
46-
config.github?.login || config.login!,
46+
getCredentials(config).github?.token,
47+
config.github?.login,
4748
config.github?.type || 'user',
4849
)
4950
}
5051

5152
return fetchGitHubSponsors(
52-
config.github?.token || config.token!,
53-
config.github?.login || config.login!,
53+
getCredentials(config).github?.token,
54+
config.github?.login,
5455
config.github?.type || 'user',
5556
config,
5657
)
5758
},
5859
}
5960

6061
export async function fetchGitHubSponsors(
61-
token: string,
62-
login: string,
62+
token: string | undefined,
63+
login: string | undefined,
6364
type: GitHubAccountType,
6465
config: ContribkitConfig,
6566
): Promise<Sponsorship[]> {
@@ -360,10 +361,15 @@ export async function fetchGitHubSponsoring(
360361
}
361362

362363
export async function fetchGitHubSponsoringAsSponsorships(
363-
token: string,
364-
login: string,
364+
token: string | undefined,
365+
login: string | undefined,
365366
type: GitHubAccountType,
366367
): Promise<Sponsorship[]> {
368+
if (!token)
369+
throw new Error('GitHub token is required')
370+
if (!login)
371+
throw new Error('GitHub login is required')
372+
367373
// Sponsees mode always loads full history regardless of active status.
368374
const records = await fetchGitHubSponsoring(token, login, type, false)
369375
const recordsBySponsorable = groupSponsoringRecordsByLogin(records)

src/providers/githubContributions.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Provider, Sponsorship } from '../types'
22
import { $fetch } from 'ofetch'
3+
import { getCredentials } from '../configs/credentials'
34

45
export const GitHubContributionsProvider: Provider = {
56
name: 'githubContributions',
@@ -8,7 +9,7 @@ export const GitHubContributionsProvider: Provider = {
89
throw new Error('GitHub login is required for githubContributions provider')
910

1011
return fetchGitHubContributions(
11-
config.githubContributions?.token || config.token!,
12+
getCredentials(config).githubContributions?.token,
1213
config.githubContributions.login,
1314
config.githubContributions.maxContributions,
1415
config.githubContributions.logarithmicScaling,
@@ -304,7 +305,7 @@ function convertToSponsorships(
304305
}
305306

306307
export async function fetchGitHubContributions(
307-
token: string,
308+
token: string | undefined,
308309
login: string,
309310
maxContributions?: number,
310311
logarithmicScaling?: boolean,

src/providers/githubContributors.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Provider, Sponsorship } from '../types'
22
import { $fetch } from 'ofetch'
3+
import { getCredentials } from '../configs/credentials'
34

45
export const GitHubContributorsProvider: Provider = {
56
name: 'githubContributors',
@@ -8,17 +9,17 @@ export const GitHubContributorsProvider: Provider = {
89
throw new Error('GitHub repository is required')
910

1011
return fetchGitHubContributors(
11-
config.githubContributors?.token || config.token!,
12-
config.githubContributors?.login || config.login!,
12+
getCredentials(config).githubContributors?.token,
13+
config.githubContributors.login,
1314
config.githubContributors.repo,
1415
config.githubContributors?.minContributions,
1516
)
1617
},
1718
}
1819

1920
export async function fetchGitHubContributors(
20-
token: string,
21-
login: string,
21+
token: string | undefined,
22+
login: string | undefined,
2223
repo: string,
2324
minContributions = 1,
2425
): Promise<Sponsorship[]> {

src/providers/gitlabContributors.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Provider, Sponsorship } from '../types'
22
import { $fetch } from 'ofetch'
3+
import { getCredentials } from '../configs/credentials'
34

45
interface GitLabContributor {
56
name: string
@@ -22,15 +23,15 @@ export const GitlabContributorsProvider: Provider = {
2223
throw new Error('Gitlab repoId is required')
2324

2425
return fetchGitlabContributors(
25-
config.gitlabContributors?.token || config.token!,
26+
getCredentials(config).gitlabContributors?.token,
2627
config.gitlabContributors.repoId,
2728
config.gitlabContributors?.minContributions,
2829
)
2930
},
3031
}
3132

3233
export async function fetchGitlabContributors(
33-
token: string,
34+
token: string | undefined,
3435
repoId: number,
3536
minContributions: number = 1,
3637
): Promise<Sponsorship[]> {

0 commit comments

Comments
 (0)