diff --git a/.github/workflows/update-new-members.yml b/.github/workflows/update-new-members.yml new file mode 100644 index 00000000..46d8b5f5 --- /dev/null +++ b/.github/workflows/update-new-members.yml @@ -0,0 +1,26 @@ +name: Update new member files + +on: + push: + branches: [vladh/schema-continued] + workflow_dispatch: + +permissions: + contents: write + pages: write + id-token: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Check out repo + uses: actions/checkout@v4 + - name: Fetch main branch + run: git fetch origin main:main + - name: Install npm packages + run: npm install + - name: Update new member files + run: bash ./bin/update-new-members + - name: Commit member changes, if any + run: bash ./bin/commit-member-changes diff --git a/bin/commit-member-changes b/bin/commit-member-changes new file mode 100755 index 00000000..533bc51d --- /dev/null +++ b/bin/commit-member-changes @@ -0,0 +1,14 @@ +#!/bin/bash -eu +# Creates a commit with the changes in `src/content/members`, if any. Must be +# run in the repo's root. + +if [[ $(git status --porcelain src/content/members) ]]; then + git config user.name 'Open Source Pledge GitHub Action Runner' + git config user.email 'bot@osspledge.com' + git add src/content/members + git commit -m 'Add changes to src/content/members (automated commit)' + git push + echo "Changes to src/content/members committed" +else + echo "No changes to local member data, skipping" +fi diff --git a/bin/update-all-members b/bin/update-all-members new file mode 100755 index 00000000..034e5756 --- /dev/null +++ b/bin/update-all-members @@ -0,0 +1,8 @@ +#!/bin/bash -eu +# This script fetches the JSON files specified in `members.csv` for all +# members. Must be run in the repo's root. + +while IFS= read -r line; do + line_parts=(${line//,/ }) + ./bin/update-member ${line_parts[0]} ${line_parts[1]} +done < members.csv diff --git a/bin/update-member b/bin/update-member new file mode 100755 index 00000000..01a184f2 --- /dev/null +++ b/bin/update-member @@ -0,0 +1,68 @@ +#!/usr/bin/env node + +import fs from 'fs'; +import fetch from 'node-fetch'; + + +const USAGE = `USAGE: ./bin/update-member foocorp https://foocorp.example.com/foocorp.json +Must be run in the repo's root.`; + + +async function updateMember(name, url) { + const localPath = `./src/content/members/${name}.json`; + console.log(`Fetching member: ${name}`); + console.log(`\t* Remote URL: ${url}`); + console.log(`\t* Local path: ${localPath}`); + + let localMemberData = undefined; + if (fs.existsSync(localPath)) { + try { + localMemberData = JSON.parse(fs.readFileSync(localPath, 'utf8')); + } catch(e) { + console.log(`Saw invalid JSON in ${localPath}, ignoring local data`); + } + } + + let remoteMemberData = undefined; + try { + remoteMemberData = await fetch(url) + .then((res) => res.text()) + .then((text) => JSON.parse(text)); + } catch (e) { + console.error(`ERROR: Failed to get JSON from ${url}`, e); + process.exit(1); + } + + let shouldWrite = (localMemberData == undefined); + + if (!shouldWrite) { + const localMtime = new Date(localMemberData.datetimeModified); + const remoteMtime = new Date(remoteMemberData.datetimeModified); + shouldWrite = remoteMtime > localMtime; + } + + if (shouldWrite) { + console.log(`Remote JSON is newer, writing to ${localPath}`); + try { + fs.writeFileSync(localPath, JSON.stringify(remoteMemberData, null, 2)); + } catch (e) { + console.error(`ERROR: Failed to write to ${localPath}`, e); + process.exit(1); + } + } else { + console.log("Remote JSON is older, skipping"); + } +} + + +function main() { + const args = process.argv.slice(2); + if (args.length != 2) { + console.error(USAGE); + process.exit(1); + } + updateMember(args[0], args[1]); +} + + +main() diff --git a/bin/update-new-members b/bin/update-new-members new file mode 100755 index 00000000..1365a0c0 --- /dev/null +++ b/bin/update-new-members @@ -0,0 +1,17 @@ +#!/bin/bash -eu +# This script fetches the JSON files specified for all members that have been +# added to `members.csv` compared to the `main` branch. Must be run in the +# repo's root. + +new_member_lines=$( + diff \ + --changed-group-format='%>' \ + --unchanged-group-format='' \ + <( git show main:members.csv ) \ + members.csv +) || true + +while IFS= read -r line; do + line_parts=(${line//,/ }) + ./bin/update-member ${line_parts[0]} ${line_parts[1]} +done <<< "$new_member_lines" diff --git a/src/content/members/sentry.json b/contrib/example-schema.json similarity index 93% rename from src/content/members/sentry.json rename to contrib/example-schema.json index 0fdeb132..5b615a18 100644 --- a/src/content/members/sentry.json +++ b/contrib/example-schema.json @@ -1,6 +1,5 @@ { "domain": "sentry.io", - "urlSource": "https://open.sentry.io/osspledge/data.json", "datetimeModified": "2024-07-19T12:24:46Z", "description": "Sentry has given to Open Source for soooooo many years.", "name": "Sentry", diff --git a/members.csv b/members.csv index 9bc9a537..ab4273f6 100644 --- a/members.csv +++ b/members.csv @@ -1 +1,2 @@ -sentry,https://open.sentry.io/funding/osspledge.json +sentry,https://raw.githubusercontent.com/opensourcepledge/opensourcepledge.com/vladh/schema-continued/contrib/example-schema.json +test,https://raw.githubusercontent.com/opensourcepledge/opensourcepledge.com/vladh/schema-continued/contrib/example-schema.json diff --git a/package-lock.json b/package-lock.json index 9dbcb235..955b2677 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@sentry/astro": "^8.18.0", "@tailwindcss/typography": "^0.5.13", "astro": "^4.11.5", + "node-fetch": "^3.3.2", "tailwindcss": "^3.4.6", "typescript": "^5.5.3" } @@ -3546,6 +3547,26 @@ "node": ">=10" } }, + "node_modules/@sentry/cli/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/@sentry/core": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.18.0.tgz", @@ -4692,6 +4713,15 @@ "node": ">=4" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/debug": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", @@ -5436,6 +5466,29 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -5485,6 +5538,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -5919,23 +5984,41 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", "dependencies": { - "whatwg-url": "^5.0.0" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, "node_modules/node-releases": { @@ -8072,7 +8155,8 @@ "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" }, "node_modules/ts-interface-checker": { "version": "0.1.13", @@ -8238,10 +8322,20 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" }, "node_modules/webpack-sources": { "version": "3.2.3", @@ -8260,6 +8354,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" diff --git a/package.json b/package.json index 5665b572..53bb2b2e 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@sentry/astro": "^8.18.0", "@tailwindcss/typography": "^0.5.13", "astro": "^4.11.5", + "node-fetch": "^3.3.2", "tailwindcss": "^3.4.6", "typescript": "^5.5.3" } diff --git a/src/content/config.ts b/src/content/config.ts index 20d85e5e..bd67bf44 100644 --- a/src/content/config.ts +++ b/src/content/config.ts @@ -38,7 +38,6 @@ export const collections = { schema: z .object({ domain: z.string(), - urlSource: z.string().url(), datetimeModified: z.string().datetime(), }) .merge(memberProvidedData), diff --git a/src/content/members/.gitkeep b/src/content/members/.gitkeep new file mode 100644 index 00000000..e69de29b