From 5e911b6c9de9c080aef44a71cede2fc302aa462d Mon Sep 17 00:00:00 2001 From: Gilbert Sanchez Date: Wed, 26 Aug 2026 22:48:17 +0000 Subject: [PATCH 1/3] Use user group content for Meetup sync --- .github/scripts/sync-meetup-events.mjs | 26 +++++++++++++++++++------- .github/workflows/meetup-sync.yml | 1 - README.md | 6 +++--- data/meetup_groups.json | 14 -------------- 4 files changed, 22 insertions(+), 25 deletions(-) delete mode 100644 data/meetup_groups.json diff --git a/.github/scripts/sync-meetup-events.mjs b/.github/scripts/sync-meetup-events.mjs index bc03f8bb7..891cf85bf 100644 --- a/.github/scripts/sync-meetup-events.mjs +++ b/.github/scripts/sync-meetup-events.mjs @@ -1,7 +1,7 @@ import { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises'; import path from 'node:path'; -const GROUPS_FILE = path.join('data', 'meetup_groups.json'); +const USER_GROUPS_DIR = path.join('content', 'user-groups'); const CALENDAR_DIR = path.join('content', 'calendar'); const DRY_RUN = process.argv.includes('--dry-run'); @@ -105,11 +105,23 @@ function eventFile(event, metadata, groupName) { } async function groups() { - const parsed = JSON.parse(await readFile(GROUPS_FILE, 'utf8')); - if (!Array.isArray(parsed) || !parsed.every((group) => group && typeof group === 'object' && typeof group.urlname === 'string' && group.urlname)) { - throw new Error(`${GROUPS_FILE} must be an array of Meetup group objects with a urlname.`); - } - return parsed; + const files = await readdir(USER_GROUPS_DIR, { withFileTypes: true }); + const urlnames = new Set(); + + await Promise.all(files + .filter((file) => file.isFile() && file.name !== '_index.md' && file.name.endsWith('.md')) + .map(async (file) => { + const content = await readFile(path.join(USER_GROUPS_DIR, file.name), 'utf8'); + const frontMatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/); + if (!frontMatter) return; + + for (const [, url] of frontMatter[1].matchAll(/^\s+url:\s*["']?(https?:\/\/[^\s"']+)["']?\s*$/gmi)) { + const match = url.match(/^https:\/\/(?:www\.)?meetup\.com\/([^/?#]+)\/?$/i); + if (match) urlnames.add(match[1]); + } + })); + + return [...urlnames]; } async function fetchGroupEvents(urlname) { @@ -141,7 +153,7 @@ async function calendarFiles() { async function main() { const configuredGroups = await groups(); - const groupEvents = await Promise.all(configuredGroups.map(({ urlname }) => fetchGroupEvents(urlname))); + const groupEvents = await Promise.all(configuredGroups.map(fetchGroupEvents)); await mkdir(CALENDAR_DIR, { recursive: true }); const calendar = await calendarFiles(); diff --git a/.github/workflows/meetup-sync.yml b/.github/workflows/meetup-sync.yml index 4ca1e480b..e9613d6ce 100644 --- a/.github/workflows/meetup-sync.yml +++ b/.github/workflows/meetup-sync.yml @@ -33,7 +33,6 @@ jobs: with: add-paths: | content/calendar - data/meetup_groups.json branch: automation/sync-meetup-events commit-message: Sync Meetup events title: Sync Meetup events diff --git a/README.md b/README.md index d3e263cf8..7f09e768b 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,9 @@ visible. Save your Markdown file and the browser updates automatically. ### Syncing Meetup user-group events -The Community Calendar automatically syncs upcoming events from configured -Meetup groups. Add the group's Meetup URL name to -[`data/meetup_groups.json`](data/meetup_groups.json), then verify its public +The Community Calendar automatically syncs upcoming events from Meetup links +in [`content/user-groups/`](content/user-groups/). Add a group's Meetup URL to +its `links` front matter, then verify its public `https://www.meetup.com//events/ical/` feed contains the intended events. The scheduled **Sync Meetup Events** workflow reads that public feed and its linked event pages; no Meetup API key or OAuth token is required. diff --git a/data/meetup_groups.json b/data/meetup_groups.json deleted file mode 100644 index cf66b2800..000000000 --- a/data/meetup_groups.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "urlname": "research-triangle-powershell-users-group" - }, - { - "urlname": "swiss-powershell-user-group" - }, - { - "urlname": "powershell-usergroup-inn-salzach" - }, - { - "urlname": "pacific-powershell-user-group" - } -] From 4e09695ba58d8402755e8f97675ff20813e78bfe Mon Sep 17 00:00:00 2001 From: Gilbert Sanchez Date: Wed, 26 Aug 2026 15:52:00 -0700 Subject: [PATCH 2/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/scripts/sync-meetup-events.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/sync-meetup-events.mjs b/.github/scripts/sync-meetup-events.mjs index 891cf85bf..ec2a18164 100644 --- a/.github/scripts/sync-meetup-events.mjs +++ b/.github/scripts/sync-meetup-events.mjs @@ -115,8 +115,8 @@ async function groups() { const frontMatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/); if (!frontMatter) return; - for (const [, url] of frontMatter[1].matchAll(/^\s+url:\s*["']?(https?:\/\/[^\s"']+)["']?\s*$/gmi)) { - const match = url.match(/^https:\/\/(?:www\.)?meetup\.com\/([^/?#]+)\/?$/i); + for (const [, url] of frontMatter[1].matchAll(/^\s*url:\s*["']?(https?:\/\/[^\s"']+)["']?\s*$/gmi)) { + const match = url.match(/^https:\/\/(?:www\.)?meetup\.com\/([^\/?#]+)(?:[\/?#]|$)/i); if (match) urlnames.add(match[1]); } })); From 88e789a3042e24b55e22a11bb6ca538909becfb8 Mon Sep 17 00:00:00 2001 From: Gilbert Sanchez Date: Wed, 26 Aug 2026 22:53:08 +0000 Subject: [PATCH 3/3] Fail Meetup sync without group links --- .github/scripts/sync-meetup-events.mjs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/scripts/sync-meetup-events.mjs b/.github/scripts/sync-meetup-events.mjs index ec2a18164..6faede4eb 100644 --- a/.github/scripts/sync-meetup-events.mjs +++ b/.github/scripts/sync-meetup-events.mjs @@ -153,6 +153,9 @@ async function calendarFiles() { async function main() { const configuredGroups = await groups(); + if (configuredGroups.length === 0) { + throw new Error(`No Meetup group links found in ${USER_GROUPS_DIR}.`); + } const groupEvents = await Promise.all(configuredGroups.map(fetchGroupEvents)); await mkdir(CALENDAR_DIR, { recursive: true });