Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dynamically calculated total amount paid to maintainers to OpenGraph image #336

Merged
merged 5 commits into from
Feb 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ jobs:
run: ./src/memberData/bin/checkMembers.ts
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: mfinelli/setup-imagemagick@v6
- name: Build generated resources
run: ./bin/make-generated-resources
- name: Build website files
uses: withastro/action@v2
deploy:
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ jobs:
run: npm install
- name: Run checks
run: npx astro check
- uses: mfinelli/setup-imagemagick@v6
- name: Build generated resources
run: ./bin/make-generated-resources
3 changes: 3 additions & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ SPDX-License-Identifier = "CC0-1.0"
path = [
"public/favicon.png",
"public/favicon.svg",
"public/generated/**",
"public/images/*",
"public/logos/*",
"public/open-source-pledge-one-pager.pdf",
Expand All @@ -45,6 +46,7 @@ SPDX-License-Identifier = "LicenseRef-Restricted"
[[annotations]]
path = [
"public/fonts/azaret-mono*",
"public/fonts/AzeretMono*",
]
SPDX-FileCopyrightText = "Copyright 2021 The Azeret Project Authors (https://github.com/displaay/azeret)"
SPDX-License-Identifier = "OFL-1.1"
Expand All @@ -53,6 +55,7 @@ SPDX-License-Identifier = "OFL-1.1"
[[annotations]]
path = [
"public/fonts/libre-franklin*",
"public/fonts/LibreFranklin*",
]
SPDX-FileCopyrightText = "Copyright (c) 2015, Impallari Type (www.impallari.com)"
SPDX-License-Identifier = "OFL-1.1"
68 changes: 68 additions & 0 deletions bin/make-generated-resources
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/bin/sh -eu

# © 2024 Functional Software, Inc. dba Sentry
# SPDX-License-Identifier: Apache-2.0

# Generates resources that depend on dynamic content, such as the OpenGraph
# image.
#
# USAGE: make-generated-resources
#
# Must be run in the repository's root directory.

get_file_age_in_sec() {
echo $(($(date +%s) - $(stat -c %Y -- "$1")))
}

# HACK: This momentarily installs fonts into the user's font folder, then
# removes them (unless they already existed).
#
# We would like to have imagemagick generate images using fonts that we
# explicitly specify by path. This would allow us to avoid polluting default
# font directories such as the user's `~/.local/share/fonts` directory. But
# there doesn't seem a way to explicitly give imagemagick font paths, outside
# of the `-font` option, which is not consulted when converting SVG text to
# PNG. imagemagick uses fontconfig to get the list of installed fonts, but
# there doesn't seem to be a way to momentarily get fontconfig to use a font,
# and indeed this might not make sense.
#
# So, for each font file:
# * put it in the user's font directory unless it's already there,
# * do our imagemagick stuff,
# * if the file is less than one hour old, remove it.

mkdir -p ~/.local/share/fonts/

for font_path in public/fonts/*.ttf; do
font_name=$(basename $font_path)
installed_font_path="$HOME/.local/share/fonts/$font_name"
if [ -f $installed_font_path ]; then
echo "Font already exists: $installed_font_path"
else
echo "Installing $installed_font_path"
cp $font_path $installed_font_path
fi
done

echo 'Generating files'

grand_total=$(./src/memberData/bin/getGrandTotalRaised.ts)
sed -i "s/\\\$[^<]\\+/${grand_total}/" public/generated/templates/opengraph.svg

echo 'Generated SVG file'
cat public/generated/templates/opengraph.svg

echo 'Generating PNG image'
magick -verbose public/generated/templates/opengraph.svg public/generated/output/opengraph.png

for font_path in public/fonts/*.ttf; do
font_name=$(basename $font_path)
installed_font_path="$HOME/.local/share/fonts/$font_name"
installed_font_age=$(get_file_age_in_sec $installed_font_path)
if [ $installed_font_age -gt 3600 ]; then
echo "Font is more than an hour old, ignoring: $installed_font_path"
else
echo "Font is less than an hour old, deleting: $installed_font_path"
rm $installed_font_path
fi
done
Binary file not shown.
Binary file added public/fonts/AzeretMono-VariableFont_wght.ttf
Binary file not shown.
Binary file not shown.
Binary file added public/fonts/LibreFranklin-VariableFont_wght.ttf
Binary file not shown.
Empty file.
2 changes: 2 additions & 0 deletions public/generated/templates/opengraph.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions public/images/endorsers/marijnh.webp.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
© 2025 Marijn Haverbeke
SPDX-License-Identifier: LicenseRef-Restricted
5 changes: 3 additions & 2 deletions src/components/GridMemberList.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
// © 2024 Vlad-Stefan Harbuz <vlad@vlad.website>
// SPDX-License-Identifier: Apache-2.0

import { getMembers } from '../memberCollections.ts';
import {
getMembers, filterInactiveMembers, sortMembersByDevs, fmtCurrency, fmtDevs, getDollarsPerDev
} from '../memberFormatting.ts';
filterInactiveMembers, sortMembersByDevs, fmtCurrency, fmtDevs, getDollarsPerDev
} from '../memberData/common.ts';
import type { Map } from '../util.ts';
import memberRoles from "../memberRoles.json"

Expand Down
5 changes: 2 additions & 3 deletions src/components/MiniLeaderboard.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
// © 2024 Vlad-Stefan Harbuz <vlad@vlad.website>
// SPDX-License-Identifier: Apache-2.0

import {
getMembers, filterInactiveMembers, sortMembersByDevs,
} from '../memberFormatting.ts';
import { getMembers } from '../memberCollections.ts';
import { filterInactiveMembers, sortMembersByDevs } from '../memberData/common.ts';

const N_TO_CHOOSE = 5;
const members = sortMembersByDevs(filterInactiveMembers(await getMembers()));
Expand Down
5 changes: 3 additions & 2 deletions src/components/TabularMemberList.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
// © 2024 Vlad-Stefan Harbuz <vlad@vlad.website>
// SPDX-License-Identifier: Apache-2.0

import { getMembers } from '../memberCollections.ts';
import {
getMembers, filterInactiveMembers, getDollarsPerDev, fmtCurrency, sortMembersByDevs
} from '../memberFormatting.ts';
filterInactiveMembers, getDollarsPerDev, fmtCurrency, sortMembersByDevs
} from '../memberData/common.ts';

const members = filterInactiveMembers(await getMembers());
---
Expand Down
7 changes: 3 additions & 4 deletions src/components/TotalTally.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@
// © 2024 Vlad-Stefan Harbuz <vlad@vlad.website>
// SPDX-License-Identifier: Apache-2.0

import {
getGrandTotalRaised, fmtCurrency,
} from '../memberFormatting.ts';
import { getGrandTotalRaised, fmtCurrency } from '../memberData/common.ts';
import { getMembers } from '../memberCollections.ts';
---

<div class="total-tally">
<p>Our members have paid maintainers</p>
<h2>{fmtCurrency(await getGrandTotalRaised())}</h2>
<h2>{fmtCurrency(getGrandTotalRaised(await getMembers()))}</h2>
<p>over the last year.</p>
</div>

Expand Down
2 changes: 1 addition & 1 deletion src/layouts/Layout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import '../styles/common.css';
content={ogDescription || "Our companies feast year after year at the Open Source table. It's time to settle up."}>
<meta
property="og:image"
content={ogImageUrl || "https://opensourcepledge.com/logos/opensourcepledge-logo-horiz-color-opengraph.png"}>
content={ogImageUrl || "https://opensourcepledge.com/generated/output/opengraph.png"}>
<meta
property="og:image:alt"
content={ogImageAlt || "The Open Source Pledge logo."}>
Expand Down
14 changes: 14 additions & 0 deletions src/memberCollections.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// © 2024 Vlad-Stefan Harbuz <vlad@vlad.website>
// SPDX-License-Identifier: Apache-2.0

import memberRoles from "./memberRoles.json"
import type { Map } from './util.ts';
import type { MemberWithId } from "./schemas/members.ts";
import { getCollection } from 'astro:content';
import { sortReportsForMemberWithId } from "./memberData/common.ts";

export async function getMembers(): Promise<MemberWithId[]> {
return (await getCollection('members'))
.filter((member) => member.id in (memberRoles as Map))
.map(sortReportsForMemberWithId);
}
37 changes: 37 additions & 0 deletions src/memberData/bin/getGrandTotalRaised.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env -S npx tsx

// © 2024 Functional Software, Inc. dba Sentry
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2025 🙃

// SPDX-License-Identifier: Apache-2.0

// Must be run in repository root.

import fs from "fs";

import memberRoles from "../../memberRoles.json";
import { sortReportsForMember, fmtCurrency } from "../common.ts";


async function main() {
let grandTotal = 0;
const memberSlugs = Object.keys(memberRoles);

for (const slug of memberSlugs) {
const localPath = `./src/content/members/${slug}.json`;

let member = undefined;
try {
member = JSON.parse(fs.readFileSync(localPath).toString());
} catch (e) {
console.error(`ERROR: could not load member data at ${localPath}`);
process.exit(1);
}

member = sortReportsForMember(member);
grandTotal += member.annualReports[0].usdAmountPaid;
}

console.log(fmtCurrency(grandTotal));
}


main()
69 changes: 69 additions & 0 deletions src/memberData/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,75 @@ const REPO_OWNER = 'opensourcepledge';
const REPO_NAME = 'opensourcepledge.com';
const EXCEPTION_LABEL = 'member-exception';

export function getDollarsPerDev(report: MemberReport) {
return report.usdAmountPaid / report.averageNumberOfDevs;
}

export function getGrandTotalRaised(members: MemberWithId[]) {
members = filterInactiveMembers(members);
let grandTotal = 0;
members.forEach((member) => {
grandTotal += member.data.annualReports[0].usdAmountPaid;
});
return grandTotal;
}

export function fmtCurrency(num: number) {
return '$' + num.toLocaleString(undefined, {
minimumFractionDigits: 0,
maximumFractionDigits: 0,
});
}

export function fmtDevs(num: number) {
return `${num} dev${num != 1 ? 's' : ''}`;
}

export function filterInactiveMembers(members: MemberWithId[]): MemberWithId[] {
return members.filter((m) => m.data.annualReports.length > 0);
}

/**
* Sorts members by the average number of devs in their latest annual report.
*/
export function sortMembersByDevs(members: MemberWithId[]): MemberWithId[] {
return [...members].sort((m1, m2) => {
if (m1.data.annualReports.length == 0) {
return 1;
}
if (m2.data.annualReports.length == 0) {
return -1;
}
const devs1 = m1.data.annualReports[0].averageNumberOfDevs;
const devs2 = m2.data.annualReports[0].averageNumberOfDevs;
if (devs1 == devs2) {
const dpd1 = getDollarsPerDev(m1.data.annualReports[0]);
const dpd2 = getDollarsPerDev(m2.data.annualReports[0]);
return dpd2 - dpd1;
} else {
return devs2 - devs1;
}
});
}

/**
* Sorts members by the dollars per dev in their latest annual report.
*/
export function sortMembersByDollarsPerDev(members: MemberWithId[]): MemberWithId[] {
return [...members].sort((m1, m2) => {
if (m1.data.annualReports.length == 0) {
return 1;
}
if (m2.data.annualReports.length == 0) {
return -1;
}
const dpd1 = getDollarsPerDev(m1.data.annualReports[0]);
const dpd2 = getDollarsPerDev(m2.data.annualReports[0]);
return dpd2 - dpd1;
});
}


export function sortReportsForMember(member: Member): Member {
const sortedReports = [...member.annualReports].sort((a, b) =>
new Date(a.year) < new Date(b.year) ? 1 : -1
Expand Down
Loading
Loading