Skip to content

Commit

Permalink
Add scripts + chart re. sponsorship activities
Browse files Browse the repository at this point in the history
  • Loading branch information
webpro committed Feb 12, 2025
1 parent 12bd803 commit de89b7e
Show file tree
Hide file tree
Showing 8 changed files with 250 additions and 2 deletions.
Binary file modified bun.lockb
Binary file not shown.
3 changes: 3 additions & 0 deletions packages/docs/astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export default defineConfig({
remarkPlugins: [fixInternalLinks, transformDirectives, remarkDirective],
rehypePlugins: [rehypeHeadingIds, rehypeAutolinkHeadings],
},
experimental: {
svg: true,
},
integrations: [
starlight({
title: 'Knip',
Expand Down
1 change: 1 addition & 0 deletions packages/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"devDependencies": {
"@astrojs/check": "0.9.4",
"@astrojs/markdown-remark": "^6.1.0",
"@octokit/graphql": "^8.2.0",
"@types/mdast": "4.0.4",
"@types/unist": "3.0.3",
"astro": "5.2.5",
Expand Down
101 changes: 101 additions & 0 deletions packages/docs/scripts/get-monthly-sponsorships-github.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { graphql } from '@octokit/graphql';

const START_DATE = new Date('2023-11-01');

interface SponsorActivity {
action: 'NEW_SPONSORSHIP' | 'CANCELLED_SPONSORSHIP';
timestamp: string;
sponsorsTier: {
monthlyPriceInDollars: number;
isOneTime: boolean;
};
sponsor: {
login: string;
};
}

interface GraphQLResponse {
viewer: {
sponsorsActivities: {
nodes: SponsorActivity[];
};
};
}

const getMonthlyTotals = async (token: string) => {
const { viewer } = await graphql<GraphQLResponse>({
query: `
query {
viewer {
sponsorsActivities(first: 100, period: ALL) {
nodes {
action
timestamp
sponsorsTier {
monthlyPriceInDollars
isOneTime
}
sponsor {
... on User { login }
... on Organization { login }
}
}
}
}
}
`,
headers: {
authorization: `token ${token}`,
},
});

const activities = [...viewer.sponsorsActivities.nodes].sort(
(a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
);

const activeRecurring = new Map<string, number>();
const monthlyTotals = new Map<string, number>();
const now = new Date();

for (let d = new Date(START_DATE); d <= now; d.setMonth(d.getMonth() + 1)) {
monthlyTotals.set(d.toISOString().substring(0, 7), 0);
}

for (const activity of activities) {
const { action, sponsor, sponsorsTier, timestamp } = activity;
const amount = sponsorsTier?.monthlyPriceInDollars || 0;
const monthYear = new Date(timestamp).toISOString().substring(0, 7);

if (sponsorsTier?.isOneTime) {
if (action === 'NEW_SPONSORSHIP' && monthYear >= START_DATE.toISOString().substring(0, 7)) {
monthlyTotals.set(monthYear, (monthlyTotals.get(monthYear) || 0) + amount);
}
} else {
if (action === 'NEW_SPONSORSHIP') activeRecurring.set(sponsor.login, amount);
else if (action === 'CANCELLED_SPONSORSHIP') activeRecurring.delete(sponsor.login);
const recurringTotal = Array.from(activeRecurring.values()).reduce((sum, a) => sum + a, 0);
for (const [month] of monthlyTotals) if (month >= monthYear) monthlyTotals.set(month, recurringTotal);
}
}

return monthlyTotals;
};

const main = async () => {
const token = process.env.GITHUB_TOKEN;
if (!token) {
throw new Error('GITHUB_TOKEN environment variable is not set');
}
const monthlyData = await getMonthlyTotals(token);

let grandTotal = 0;

for (const [month, amount] of [...monthlyData.entries()].sort()) {
console.log(`${month} ${amount}`);
grandTotal += amount;
}

console.log(`\nGrand total: ${grandTotal} (${monthlyData.size} months)`);
};

main().catch(console.error);
119 changes: 119 additions & 0 deletions packages/docs/scripts/get-monthly-sponsorships-opencollective.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { graphql } from '@octokit/graphql';

const START_DATE = new Date('2023-11-01');
const RATE_EUR_TO_USD = 1.08;

interface Transaction {
id: string;
type: string;
kind: string;
amount: {
value: number;
currency: string;
};
createdAt: string;
fromAccount: {
name: string;
};
}

interface Expense {
id: string;
amount: number;
currency: string;
createdAt: string;
type: string;
status: string;
}

interface GraphQLResponse {
account: {
transactions: {
nodes: Transaction[];
};
};
expenses: {
nodes: Expense[];
};
}

const getMonthlyTotals = async (token: string): Promise<Map<string, number>> => {
const { account, expenses } = await graphql<GraphQLResponse>({
query: `
query {
account(slug: "knip") {
transactions(type: CREDIT) {
nodes {
id
type
kind
amount {
value
currency
}
createdAt
fromAccount {
name
}
}
}
}
expenses(fromAccount: { slug: "webpro" }) {
nodes {
id
amount
currency
createdAt
type
status
}
}
}
`,
url: 'https://api.opencollective.com/graphql/v2',
headers: {
'Api-Key': token,
Accept: 'application/json',
},
});

const monthlyTotals = new Map<string, number>();
const now = new Date();

for (let d = new Date(START_DATE); d <= now; d.setMonth(d.getMonth() + 1)) {
monthlyTotals.set(d.toISOString().substring(0, 7), 0);
}

for (const transaction of account.transactions.nodes) {
const month = new Date(transaction.createdAt).toISOString().substring(0, 7);
const amount = Math.round(transaction.amount.value);
monthlyTotals.set(month, (monthlyTotals.get(month) || 0) + amount);
}

for (const expense of expenses.nodes) {
const month = new Date(expense.createdAt).toISOString().substring(0, 7);
const amount =
expense.currency === 'EUR'
? Math.round((expense.amount / 100) * RATE_EUR_TO_USD)
: Math.round(expense.amount / 100);
monthlyTotals.set(month, (monthlyTotals.get(month) || 0) + amount);
}

return monthlyTotals;
};

const main = async () => {
const token = process.env.OPENCOLLECTIVE_TOKEN;
if (!token) throw new Error('OPENCOLLECTIVE_TOKEN is not set');
const monthlyData = await getMonthlyTotals(token);

let grandTotal = 0;
for (const [month, amount] of [...monthlyData.entries()].sort()) {
console.log(`${month} ${amount}`);
grandTotal += amount;
}

console.log(`\nGrand total: ${grandTotal} (${monthlyData.size} months)`);
};

main().catch(console.error);
7 changes: 7 additions & 0 deletions packages/docs/src/assets/venz-chart.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions packages/docs/src/components/SponsorsChart.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
import Chart from '../assets/venz-chart.svg';
---

<Chart />
16 changes: 14 additions & 2 deletions packages/docs/src/pages/sponsors.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro';
import Posts from '../components/Posts.astro';
import Projects from '../components/Projects.astro';
import SponsorsComponent from '../components/Sponsors.astro';
import SponsorsChart from '../components/SponsorsChart.astro';
import SponsorsPast from '../components/SponsorsPast.astro';
---

Expand Down Expand Up @@ -38,13 +39,12 @@ import SponsorsPast from '../components/SponsorsPast.astro';
<ul>
<li><a href="https://github.com/sponsors/webpro">GitHub Sponsors</a></li>
<li><a href="https://opencollective.com/knip">OpenCollective</a></li>
<li><a href="https://polar.sh/webpro-nl">Polar</a></li>
</ul>

<p>
You or your company logo will be added to this page if you decide to
support the project on an ongoing basis. Eternal gratitude to the
companies and people that are already supporters:
companies and people supporting the project!
</p>

</div>
Expand All @@ -57,6 +57,18 @@ import SponsorsPast from '../components/SponsorsPast.astro';

</article>

<h2 id="projects-using-knip">🧡 Monthly Overview</h2>

<p>
Overview of gross debit GitHub and OpenCollective sponsorships and invoices,
since it was openly asked for. The monthly aggregated average over the
charted period is $533, actual development started one year before that.
GitHub Sponsors is for my GitHub account, which has more repositories, but
in practice targets mostly Knip.
</p>

<SponsorsChart />

<h2 id="projects-using-knip">🧡 Past Sponsors</h2>

<div class="sponsors">
Expand Down

0 comments on commit de89b7e

Please sign in to comment.