From 4123ef1511b14d8c2c75fb06a900df4a22e6fec7 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:59:21 -0400 Subject: [PATCH 1/2] 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. --- README.md | 3 +- src/configs/credentials.ts | 69 ++++++++++++++++++++++++ src/configs/env.ts | 26 ++++----- src/configs/index.ts | 14 ++--- src/providers/afdian.ts | 6 +-- src/providers/crowdinContributors.ts | 4 +- src/providers/github.ts | 22 +++++--- src/providers/githubContributions.ts | 5 +- src/providers/githubContributors.ts | 9 ++-- src/providers/gitlabContributors.ts | 5 +- src/providers/index.ts | 14 ++--- src/providers/opencollective.ts | 5 +- src/providers/patreon.ts | 5 +- src/providers/polar.ts | 5 +- src/types.ts | 79 +--------------------------- 15 files changed, 136 insertions(+), 135 deletions(-) create mode 100644 src/configs/credentials.ts diff --git a/README.md b/README.md index 37f3e50f..047e94e3 100644 --- a/README.md +++ b/README.md @@ -225,13 +225,14 @@ import { fetchSponsors } from '@lizardbyte/contribkit' const sponsors = await fetchSponsors({ github: { - token, login, }, // ... }) ``` +Set credentials with environment variables such as `CONTRIBKIT_GITHUB_TOKEN`. + Check the type definition or source code for more utils available. ### Renderers diff --git a/src/configs/credentials.ts b/src/configs/credentials.ts new file mode 100644 index 00000000..774b0b58 --- /dev/null +++ b/src/configs/credentials.ts @@ -0,0 +1,69 @@ +import type { ContribkitConfig } from '../types' +import process from 'node:process' +import dotenv from 'dotenv' + +export interface EnvCredentials { + github?: { + token?: string + } + patreon?: { + token?: string + } + opencollective?: { + key?: string + } + afdian?: { + token?: string + } + polar?: { + token?: string + } + githubContributors?: { + token?: string + } + gitlabContributors?: { + token?: string + } + githubContributions?: { + token?: string + } +} + +interface ConfigWithCredentials extends ContribkitConfig { + credentials?: EnvCredentials +} + +export function loadEnvCredentials(): EnvCredentials { + dotenv.config({ quiet: true }) + + return JSON.parse(JSON.stringify({ + github: { + token: process.env.CONTRIBKIT_GITHUB_TOKEN || process.env.GITHUB_TOKEN, + }, + patreon: { + token: process.env.CONTRIBKIT_PATREON_TOKEN || process.env.PATREON_TOKEN, + }, + opencollective: { + key: process.env.CONTRIBKIT_OPENCOLLECTIVE_KEY || process.env.OPENCOLLECTIVE_KEY, + }, + afdian: { + token: process.env.CONTRIBKIT_AFDIAN_TOKEN || process.env.AFDIAN_TOKEN, + }, + polar: { + token: process.env.CONTRIBKIT_POLAR_TOKEN || process.env.POLAR_TOKEN, + }, + githubContributors: { + token: process.env.CONTRIBKIT_GITHUB_CONTRIBUTORS_TOKEN, + }, + gitlabContributors: { + token: process.env.CONTRIBKIT_GITLAB_CONTRIBUTORS_TOKEN, + }, + githubContributions: { + token: process.env.CONTRIBKIT_GITHUB_CONTRIBUTIONS_TOKEN, + }, + })) +} + +export function getCredentials(config: ContribkitConfig): EnvCredentials { + return (config as ConfigWithCredentials).credentials ?? loadEnvCredentials() +} diff --git a/src/configs/env.ts b/src/configs/env.ts index 5a9a151d..5a8ac1a4 100644 --- a/src/configs/env.ts +++ b/src/configs/env.ts @@ -1,29 +1,23 @@ import type { ContribkitConfig, GitHubAccountType } from '../types' import process from 'node:process' import dotenv from 'dotenv' +import { loadEnvCredentials } from './credentials' -function getDeprecatedEnv(name: string, replacement: string) { - const value = process.env[name] - if (value) - console.warn(`[contribkit] env.${name} is deprecated, use env.${replacement} instead`) - return value +export interface EnvConfig { + config: Partial + credentials: ReturnType } -export function loadEnv(): Partial { +export function loadEnv(): EnvConfig { dotenv.config({ quiet: true }) const config: Partial = { mode: process.env.CONTRIBKIT_MODE as ContribkitConfig['mode'] | undefined, github: { login: process.env.CONTRIBKIT_GITHUB_LOGIN || process.env.GITHUB_LOGIN, - token: process.env.CONTRIBKIT_GITHUB_TOKEN || process.env.GITHUB_TOKEN, type: (process.env.CONTRIBKIT_GITHUB_TYPE || process.env.GITHUB_TYPE) as GitHubAccountType | undefined, }, - patreon: { - token: process.env.CONTRIBKIT_PATREON_TOKEN || process.env.PATREON_TOKEN, - }, opencollective: { - key: process.env.CONTRIBKIT_OPENCOLLECTIVE_KEY || process.env.OPENCOLLECTIVE_KEY, id: process.env.CONTRIBKIT_OPENCOLLECTIVE_ID || process.env.OPENCOLLECTIVE_ID, slug: process.env.CONTRIBKIT_OPENCOLLECTIVE_SLUG || process.env.OPENCOLLECTIVE_SLUG, githubHandle: process.env.CONTRIBKIT_OPENCOLLECTIVE_GH_HANDLE || process.env.OPENCOLLECTIVE_GH_HANDLE, @@ -31,11 +25,9 @@ export function loadEnv(): Partial { }, afdian: { userId: process.env.CONTRIBKIT_AFDIAN_USER_ID || process.env.AFDIAN_USER_ID, - token: process.env.CONTRIBKIT_AFDIAN_TOKEN || process.env.AFDIAN_TOKEN, exchangeRate: Number.parseFloat(process.env.CONTRIBKIT_AFDIAN_EXCHANGE_RATE || process.env.AFDIAN_EXCHANGE_RATE || '0') || undefined, }, polar: { - token: process.env.CONTRIBKIT_POLAR_TOKEN || process.env.POLAR_TOKEN, organization: process.env.CONTRIBKIT_POLAR_ORGANIZATION || process.env.POLAR_ORGANIZATION, }, liberapay: { @@ -44,12 +36,10 @@ export function loadEnv(): Partial { outputDir: process.env.CONTRIBKIT_DIR, githubContributors: { login: process.env.CONTRIBKIT_GITHUB_CONTRIBUTORS_LOGIN, - token: process.env.CONTRIBKIT_GITHUB_CONTRIBUTORS_TOKEN, minContributions: Number(process.env.CONTRIBKIT_GITHUB_CONTRIBUTORS_MIN) || 1, repo: process.env.CONTRIBKIT_GITHUB_CONTRIBUTORS_REPO, }, gitlabContributors: { - token: process.env.CONTRIBKIT_GITLAB_CONTRIBUTORS_TOKEN, minContributions: Number(process.env.CONTRIBKIT_GITLAB_CONTRIBUTORS_MIN) || 1, repoId: Number(process.env.CONTRIBKIT_GITLAB_CONTRIBUTORS_REPO_ID), }, @@ -60,12 +50,14 @@ export function loadEnv(): Partial { }, githubContributions: { login: process.env.CONTRIBKIT_GITHUB_CONTRIBUTIONS_LOGIN, - token: process.env.CONTRIBKIT_GITHUB_CONTRIBUTIONS_TOKEN, maxContributions: Number(process.env.CONTRIBKIT_GITHUB_CONTRIBUTIONS_MAX) || undefined, logarithmicScaling: process.env.CONTRIBKIT_GITHUB_CONTRIBUTIONS_LOGARITHMIC === 'true', }, } // remove undefined keys - return JSON.parse(JSON.stringify(config)) + return { + config: JSON.parse(JSON.stringify(config)), + credentials: loadEnvCredentials(), + } } diff --git a/src/configs/index.ts b/src/configs/index.ts index f690982a..d7d52505 100644 --- a/src/configs/index.ts +++ b/src/configs/index.ts @@ -14,6 +14,7 @@ export function defineConfig(config: ContribkitConfig): ContribkitConfig { export async function loadConfig(inlineConfig: ContribkitConfig = {}): Promise> { const env = loadEnv() + const envConfig = env.config const { config = {} } = await _loadConfig({ sources: [ @@ -39,35 +40,36 @@ export async function loadConfig(inlineConfig: ContribkitConfig = {}): Promise if (!['sponsors', 'sponsees'].includes(resolved.mode)) throw new Error(`Invalid mode: ${resolved.mode}. Expected "sponsors" or "sponsees".`) - resolved.name = inlineConfig.name || config.name || env.name || resolved.mode + resolved.name = inlineConfig.name || config.name || envConfig.name || resolved.mode return resolved } diff --git a/src/providers/afdian.ts b/src/providers/afdian.ts index 2c47edc1..3dc915ee 100644 --- a/src/providers/afdian.ts +++ b/src/providers/afdian.ts @@ -1,6 +1,7 @@ import type { ContribkitConfig, Provider, Sponsorship } from '../types' import { createHash } from 'node:crypto' import { $fetch } from 'ofetch' +import { getCredentials } from '../configs/credentials' // afdian api docs https://afdian.net/p/9c65d9cc617011ed81c352540025c377 @@ -12,14 +13,13 @@ export const AfdianProvider: Provider = { return Promise.resolve([]) } - return fetchAfdianSponsors(config.afdian) + return fetchAfdianSponsors(config.afdian, getCredentials(config).afdian?.token) }, } -export async function fetchAfdianSponsors(options: ContribkitConfig['afdian'] = {}): Promise { +export async function fetchAfdianSponsors(options: ContribkitConfig['afdian'] = {}, token = getCredentials({}).afdian?.token): Promise { const { userId, - token, exchangeRate = 6.5, includePurchases = true, purchaseEffectivity = 30, diff --git a/src/providers/crowdinContributors.ts b/src/providers/crowdinContributors.ts index 90c7c79c..7bf7ed2c 100644 --- a/src/providers/crowdinContributors.ts +++ b/src/providers/crowdinContributors.ts @@ -14,7 +14,7 @@ export const CrowdinContributorsProvider: Provider = { name: 'crowdinContributors', fetchSponsors(config) { return fetchCrowdinContributors( - config.crowdinContributors?.token || config.token!, + config.crowdinContributors?.token, config.crowdinContributors?.projectId || 0, config.crowdinContributors?.minTranslations || 1, ) @@ -22,7 +22,7 @@ export const CrowdinContributorsProvider: Provider = { } export async function fetchCrowdinContributors( - token: string, + token: string | undefined, projectId: number, minTranslations = 1, ): Promise { diff --git a/src/providers/github.ts b/src/providers/github.ts index dcf6c1c7..449a061b 100644 --- a/src/providers/github.ts +++ b/src/providers/github.ts @@ -1,5 +1,6 @@ import type { ContribkitConfig, GitHubAccountType, Provider, Sponsorship, Tier } from '../types' import { $fetch } from 'ofetch' +import { getCredentials } from '../configs/credentials' import { normalizeUrl } from '../utils' function getMonthDifference(startDate: Date, endDate: Date) { @@ -42,15 +43,15 @@ export const GitHubProvider: Provider = { fetchSponsors(config) { if (config.mode === 'sponsees') { return fetchGitHubSponsoringAsSponsorships( - config.github?.token || config.token!, - config.github?.login || config.login!, + getCredentials(config).github?.token, + config.github?.login, config.github?.type || 'user', ) } return fetchGitHubSponsors( - config.github?.token || config.token!, - config.github?.login || config.login!, + getCredentials(config).github?.token, + config.github?.login, config.github?.type || 'user', config, ) @@ -58,8 +59,8 @@ export const GitHubProvider: Provider = { } export async function fetchGitHubSponsors( - token: string, - login: string, + token: string | undefined, + login: string | undefined, type: GitHubAccountType, config: ContribkitConfig, ): Promise { @@ -360,10 +361,15 @@ export async function fetchGitHubSponsoring( } export async function fetchGitHubSponsoringAsSponsorships( - token: string, - login: string, + token: string | undefined, + login: string | undefined, type: GitHubAccountType, ): Promise { + if (!token) + throw new Error('GitHub token is required') + if (!login) + throw new Error('GitHub login is required') + // Sponsees mode always loads full history regardless of active status. const records = await fetchGitHubSponsoring(token, login, type, false) const recordsBySponsorable = groupSponsoringRecordsByLogin(records) diff --git a/src/providers/githubContributions.ts b/src/providers/githubContributions.ts index 262bbab8..95afa8b0 100644 --- a/src/providers/githubContributions.ts +++ b/src/providers/githubContributions.ts @@ -1,5 +1,6 @@ import type { Provider, Sponsorship } from '../types' import { $fetch } from 'ofetch' +import { getCredentials } from '../configs/credentials' export const GitHubContributionsProvider: Provider = { name: 'githubContributions', @@ -8,7 +9,7 @@ export const GitHubContributionsProvider: Provider = { throw new Error('GitHub login is required for githubContributions provider') return fetchGitHubContributions( - config.githubContributions?.token || config.token!, + getCredentials(config).githubContributions?.token, config.githubContributions.login, config.githubContributions.maxContributions, config.githubContributions.logarithmicScaling, @@ -304,7 +305,7 @@ function convertToSponsorships( } export async function fetchGitHubContributions( - token: string, + token: string | undefined, login: string, maxContributions?: number, logarithmicScaling?: boolean, diff --git a/src/providers/githubContributors.ts b/src/providers/githubContributors.ts index be170b09..5ee56a35 100644 --- a/src/providers/githubContributors.ts +++ b/src/providers/githubContributors.ts @@ -1,5 +1,6 @@ import type { Provider, Sponsorship } from '../types' import { $fetch } from 'ofetch' +import { getCredentials } from '../configs/credentials' export const GitHubContributorsProvider: Provider = { name: 'githubContributors', @@ -8,8 +9,8 @@ export const GitHubContributorsProvider: Provider = { throw new Error('GitHub repository is required') return fetchGitHubContributors( - config.githubContributors?.token || config.token!, - config.githubContributors?.login || config.login!, + getCredentials(config).githubContributors?.token, + config.githubContributors.login, config.githubContributors.repo, config.githubContributors?.minContributions, ) @@ -17,8 +18,8 @@ export const GitHubContributorsProvider: Provider = { } export async function fetchGitHubContributors( - token: string, - login: string, + token: string | undefined, + login: string | undefined, repo: string, minContributions = 1, ): Promise { diff --git a/src/providers/gitlabContributors.ts b/src/providers/gitlabContributors.ts index 33d5c4b7..92cf6375 100644 --- a/src/providers/gitlabContributors.ts +++ b/src/providers/gitlabContributors.ts @@ -1,5 +1,6 @@ import type { Provider, Sponsorship } from '../types' import { $fetch } from 'ofetch' +import { getCredentials } from '../configs/credentials' interface GitLabContributor { name: string @@ -22,7 +23,7 @@ export const GitlabContributorsProvider: Provider = { throw new Error('Gitlab repoId is required') return fetchGitlabContributors( - config.gitlabContributors?.token || config.token!, + getCredentials(config).gitlabContributors?.token, config.gitlabContributors.repoId, config.gitlabContributors?.minContributions, ) @@ -30,7 +31,7 @@ export const GitlabContributorsProvider: Provider = { } export async function fetchGitlabContributors( - token: string, + token: string | undefined, repoId: number, minContributions: number = 1, ): Promise { diff --git a/src/providers/index.ts b/src/providers/index.ts index 613dd39f..b0b86be4 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -1,4 +1,5 @@ import type { ContribkitConfig, Provider, ProviderName } from '../types' +import { getCredentials } from '../configs/credentials' import { AfdianProvider } from './afdian' import { CrowdinContributorsProvider } from './crowdinContributors' import { GitHubProvider } from './github' @@ -27,31 +28,32 @@ export const ProvidersMap = { export function guessProviders(config: ContribkitConfig) { const items: ProviderName[] = [] + const credentials = getCredentials(config) if (config.github && config.github.login) items.push('github') - if (config.patreon && config.patreon.token) + if (credentials.patreon?.token) items.push('patreon') if (config.opencollective && (config.opencollective.id || config.opencollective.slug || config.opencollective.githubHandle)) items.push('opencollective') - if (config.afdian && config.afdian.userId && config.afdian.token) + if (config.afdian && config.afdian.userId && credentials.afdian?.token) items.push('afdian') - if (config.polar && config.polar.token) + if (credentials.polar?.token) items.push('polar') if (config.liberapay && config.liberapay.login) items.push('liberapay') - if (config.githubContributors?.login && config.githubContributors?.token) + if (config.githubContributors?.login && credentials.githubContributors?.token) items.push('githubContributors') - if (config.githubContributions?.login && config.githubContributions?.token) + if (config.githubContributions?.login && credentials.githubContributions?.token) items.push('githubContributions') - if (config.gitlabContributors?.token && config.gitlabContributors?.repoId) + if (credentials.gitlabContributors?.token && config.gitlabContributors?.repoId) items.push('gitlabContributors') if (config.crowdinContributors?.token && config.crowdinContributors?.projectId) diff --git a/src/providers/opencollective.ts b/src/providers/opencollective.ts index 373bed7c..10a2f933 100644 --- a/src/providers/opencollective.ts +++ b/src/providers/opencollective.ts @@ -1,5 +1,6 @@ import type { Provider, Sponsorship } from '../types' import { $fetch } from 'ofetch' +import { getCredentials } from '../configs/credentials' import { normalizeUrl } from '../utils' interface SocialLink { @@ -13,7 +14,7 @@ export const OpenCollectiveProvider: Provider = { if (config.mode === 'sponsees') { const slug = config.opencollective?.slug || config.opencollective?.id return fetchOpenCollectiveSponsors( - config.opencollective?.key, + getCredentials(config).opencollective?.key, undefined, slug, config.opencollective?.githubHandle, @@ -23,7 +24,7 @@ export const OpenCollectiveProvider: Provider = { } return fetchOpenCollectiveSponsors( - config.opencollective?.key, + getCredentials(config).opencollective?.key, config.opencollective?.id, config.opencollective?.slug, config.opencollective?.githubHandle, diff --git a/src/providers/patreon.ts b/src/providers/patreon.ts index f1659152..b1126801 100644 --- a/src/providers/patreon.ts +++ b/src/providers/patreon.ts @@ -1,5 +1,6 @@ import type { Provider, Sponsorship } from '../types' import { $fetch } from 'ofetch' +import { getCredentials } from '../configs/credentials' export const PatreonProvider: Provider = { name: 'patreon', @@ -9,11 +10,11 @@ export const PatreonProvider: Provider = { return Promise.resolve([]) } - return fetchPatreonSponsors(config.patreon?.token || config.token!) + return fetchPatreonSponsors(getCredentials(config).patreon?.token) }, } -export async function fetchPatreonSponsors(token: string): Promise { +export async function fetchPatreonSponsors(token: string | undefined): Promise { if (!token) throw new Error('Patreon token is required') diff --git a/src/providers/polar.ts b/src/providers/polar.ts index 8ea687a1..97fddaad 100644 --- a/src/providers/polar.ts +++ b/src/providers/polar.ts @@ -1,5 +1,6 @@ import type { Provider, Sponsorship } from '../types' import { ofetch } from 'ofetch' +import { getCredentials } from '../configs/credentials' export const PolarProvider: Provider = { name: 'polar', @@ -9,11 +10,11 @@ export const PolarProvider: Provider = { return Promise.resolve([]) } - return fetchPolarSponsors(config.polar?.token || config.token!, config.polar?.organization) + return fetchPolarSponsors(getCredentials(config).polar?.token, config.polar?.organization) }, } -export async function fetchPolarSponsors(token: string, organization?: string): Promise { +export async function fetchPolarSponsors(token: string | undefined, organization?: string): Promise { if (!token) throw new Error('Polar token is required') if (!organization) diff --git a/src/types.ts b/src/types.ts index 96392fe1..a85df637 100644 --- a/src/types.ts +++ b/src/types.ts @@ -88,14 +88,6 @@ export interface ProvidersConfig { * Will read from `CONTRIBKIT_GITHUB_LOGIN` environment variable if not set. */ login?: string - /** - * GitHub Token that have access to your sponsorships. - * - * Will read from `CONTRIBKIT_GITHUB_TOKEN` environment variable if not set. - * - * @deprecated It's not recommended set this value directly, pass from env or use `.env` file. - */ - token?: string /** * The account type for sponsorships. * @@ -104,25 +96,8 @@ export interface ProvidersConfig { */ type?: GitHubAccountType } - patreon?: { - /** - * Patreon Token that have access to your sponsorships. - * - * Will read from `CONTRIBKIT_PATREON_TOKEN` environment variable if not set. - * - * @deprecated It's not recommended set this value directly, pass from env or use `.env` file. - */ - token?: string - } + patreon?: Record opencollective?: { - /** - * Api key of your OpenCollective account. - * - * Will read from `CONTRIBKIT_OPENCOLLECTIVE_KEY` environment variable if not set. - * - * @deprecated It's not recommended set this value directly, pass from env or use `.env` file. - */ - key?: string /** * The id of your account. * @@ -157,15 +132,6 @@ export interface ProvidersConfig { * @see https://afdian.net/dashboard/dev */ userId?: string - /** - * Afdian Token that have access to your sponsorships. - * - * Will read from `CONTRIBKIT_AFDIAN_TOKEN` environment variable if not set. - * - * @see https://afdian.net/dashboard/dev - * @deprecated It's not recommended set this value directly, pass from env or use `.env` file. - */ - token?: string /** * Exchange rate of USD to CNY * @@ -185,15 +151,6 @@ export interface ProvidersConfig { } polar?: { - /** - * Polar token that have access to your sponsorships. - * - * Will read from `CONTRIBKIT_POLAR_TOKEN` environment variable if not set. - * - * @see https://polar.sh/settings - * @deprecated It's not recommended set this value directly, pass from env or use `.env` file. - */ - token?: string /** * The name of the organization to fetch sponsorships from. If not set, it will fetch the sponsorships of the user. * @@ -218,14 +175,6 @@ export interface ProvidersConfig { * Will read from `CONTRIBKIT_GITHUB_CONTRIBUTORS_LOGIN` environment variable if not set. */ login?: string - /** - * GitHub Token that have access to your sponsorships. - * - * Will read from `CONTRIBKIT_GITHUB_CONTRIBUTORS_TOKEN` environment variable if not set. - * - * @deprecated It's not recommended set this value directly, pass from env or use `.env` file. - */ - token?: string /** * The minimum number of contributions to be considered a sponsor. * @@ -241,14 +190,6 @@ export interface ProvidersConfig { } gitlabContributors?: { - /** - * Gitlab Token that have access contributors. - * - * Will read from `CONTRIBKIT_GITLAB_CONTRIBUTORS_TOKEN` environment variable if not set. - * - * @deprecated It's not recommended set this value directly, pass from env or use `.env` file. - */ - token?: string /** * The minimum number of contributions to be considered a sponsor. * @@ -291,14 +232,6 @@ export interface ProvidersConfig { * Will read from `CONTRIBKIT_GITHUB_CONTRIBUTIONS_LOGIN` environment variable if not set. */ login?: string - /** - * GitHub Token that has access to read user contributions. - * - * Will read from `CONTRIBKIT_GITHUB_CONTRIBUTIONS_TOKEN` environment variable if not set. - * - * @deprecated It's not recommended set this value directly, pass from env or use `.env` file. - */ - token?: string /** * Cap the maximum contribution count per organization/user. * Useful to prevent one dominant contributor from overshadowing others in visualizations. @@ -425,16 +358,6 @@ export interface ContribkitConfig extends ProvidersConfig, ContribkitRenderOptio */ mode?: SponsorshipMode - /** - * @deprecated use `github.login` instead - */ - login?: string - - /** - * @deprecated use `github.token` instead - */ - token?: string - /** * @default auto detect based on the config provided */ From db57c1bb720c92d68d7e76f6f3d51b00dffe7642 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 10 Jun 2026 20:04:29 -0400 Subject: [PATCH 2/2] Prune undefined env values and fix afdian check Introduce a recursive pruneUndefined helper to remove undefined values from env/config objects instead of using JSON.stringify/parse. Apply it in loadEnvCredentials and loadEnv to produce clean credential/config shapes. Also tighten the afdian presence check in providers (use optional chaining) to avoid errors when config.afdian is absent. --- src/configs/credentials.ts | 18 ++++++++++++++++-- src/configs/env.ts | 4 ++-- src/providers/index.ts | 2 +- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/configs/credentials.ts b/src/configs/credentials.ts index 774b0b58..caee0cd2 100644 --- a/src/configs/credentials.ts +++ b/src/configs/credentials.ts @@ -33,10 +33,24 @@ interface ConfigWithCredentials extends ContribkitConfig { credentials?: EnvCredentials } +export function pruneUndefined(value: T): T { + if (Array.isArray(value)) + return value.map(item => pruneUndefined(item)) as T + + if (!value || typeof value !== 'object') + return value + + return Object.fromEntries( + Object.entries(value) + .filter(([, item]) => item !== undefined) + .map(([key, item]) => [key, pruneUndefined(item)]), + ) as T +} + export function loadEnvCredentials(): EnvCredentials { dotenv.config({ quiet: true }) - return JSON.parse(JSON.stringify({ + return pruneUndefined({ github: { token: process.env.CONTRIBKIT_GITHUB_TOKEN || process.env.GITHUB_TOKEN, }, @@ -61,7 +75,7 @@ export function loadEnvCredentials(): EnvCredentials { githubContributions: { token: process.env.CONTRIBKIT_GITHUB_CONTRIBUTIONS_TOKEN, }, - })) + }) } export function getCredentials(config: ContribkitConfig): EnvCredentials { diff --git a/src/configs/env.ts b/src/configs/env.ts index 5a8ac1a4..d3b9affb 100644 --- a/src/configs/env.ts +++ b/src/configs/env.ts @@ -1,7 +1,7 @@ import type { ContribkitConfig, GitHubAccountType } from '../types' import process from 'node:process' import dotenv from 'dotenv' -import { loadEnvCredentials } from './credentials' +import { loadEnvCredentials, pruneUndefined } from './credentials' export interface EnvConfig { config: Partial @@ -57,7 +57,7 @@ export function loadEnv(): EnvConfig { // remove undefined keys return { - config: JSON.parse(JSON.stringify(config)), + config: pruneUndefined(config), credentials: loadEnvCredentials(), } } diff --git a/src/providers/index.ts b/src/providers/index.ts index b0b86be4..db10d179 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -38,7 +38,7 @@ export function guessProviders(config: ContribkitConfig) { if (config.opencollective && (config.opencollective.id || config.opencollective.slug || config.opencollective.githubHandle)) items.push('opencollective') - if (config.afdian && config.afdian.userId && credentials.afdian?.token) + if (config.afdian?.userId && credentials.afdian?.token) items.push('afdian') if (credentials.polar?.token)