From 0679aa013f527b6878f197a0c90336b13076de1f Mon Sep 17 00:00:00 2001 From: beo3000 Date: Wed, 6 May 2026 21:08:08 +0200 Subject: [PATCH] feat(scripts): add backfill-series-id script for jour-fixe agendas Co-Authored-By: Claude Sonnet 4.6 --- scripts/backfill-series-id.js | 55 +++++++++++++++++++++++++ scripts/test/backfill-series-id.test.js | 27 ++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 scripts/backfill-series-id.js create mode 100644 scripts/test/backfill-series-id.test.js diff --git a/scripts/backfill-series-id.js b/scripts/backfill-series-id.js new file mode 100644 index 0000000..a3cd684 --- /dev/null +++ b/scripts/backfill-series-id.js @@ -0,0 +1,55 @@ +// scripts/backfill-series-id.js +const { readFileSync, writeFileSync } = require('node:fs'); +const { resolve } = require('node:path'); +const { getCalendarEvents } = require('./lib/o365-calendar.js'); + +function upsertFrontmatterField(md, key, value) { + const fmMatch = md.match(/^---\n([\s\S]*?)\n---/); + if (!fmMatch) return md; + const fm = fmMatch[1]; + const lineRegex = new RegExp(`^${key}:.*$`, 'm'); + let newFm; + if (lineRegex.test(fm)) { + newFm = fm.replace(lineRegex, `${key}: ${value}`); + } else { + newFm = `${fm}\n${key}: ${value}`; + } + return md.replace(/^---\n[\s\S]*?\n---/, `---\n${newFm}\n---`); +} + +async function backfill(agendaPath, titleRegex, daysAhead = 30) { + const events = await getCalendarEvents(daysAhead); + const re = new RegExp(titleRegex, 'i'); + const match = events.find(e => re.test(e.title) && e.isRecurring); + if (!match) { + throw new Error(`no recurring event matching /${titleRegex}/ in next ${daysAhead} days`); + } + // Need raw seriesMasterId — re-fetch single event by id (parseEventToMeeting drops it) + const { getEventById } = require('./lib/o365-calendar.js'); + const raw = await getEventById(match.id); + const seriesId = raw.seriesMasterId; + if (!seriesId) { + throw new Error(`event ${match.id} has no seriesMasterId`); + } + const md = readFileSync(agendaPath, 'utf-8'); + const updated = upsertFrontmatterField(md, 'o365_series_id', seriesId); + writeFileSync(agendaPath, updated, 'utf-8'); + return seriesId; +} + +async function main() { + const args = process.argv.slice(2); + if (args.length < 2) { + console.error('Usage: node backfill-series-id.js '); + process.exit(2); + } + const [agendaPath, titleRegex] = args; + const id = await backfill(resolve(agendaPath), titleRegex); + console.log(`set o365_series_id=${id} in ${agendaPath}`); +} + +if (require.main === module) { + main().catch(e => { console.error(e.stack || e.message); process.exit(1); }); +} + +module.exports = { upsertFrontmatterField, backfill }; diff --git a/scripts/test/backfill-series-id.test.js b/scripts/test/backfill-series-id.test.js new file mode 100644 index 0000000..dceecc8 --- /dev/null +++ b/scripts/test/backfill-series-id.test.js @@ -0,0 +1,27 @@ +// scripts/test/backfill-series-id.test.js +const { describe, it } = require('node:test'); +const assert = require('node:assert/strict'); +const { upsertFrontmatterField } = require('../backfill-series-id.js'); + +describe('upsertFrontmatterField', () => { + it('inserts new field when missing', () => { + const md = `---\ntags: [jour-fixe]\nserie: IT Team\nrhythmus: wöchentlich\n---\n\n# Agenda\n`; + const out = upsertFrontmatterField(md, 'o365_series_id', 'AAMkSeries123'); + assert.ok(out.includes('o365_series_id: AAMkSeries123')); + assert.ok(out.includes('serie: IT Team')); + assert.ok(out.endsWith('# Agenda\n')); + }); + + it('replaces existing field value', () => { + const md = `---\nserie: X\no365_series_id: OLD\n---\n\nbody\n`; + const out = upsertFrontmatterField(md, 'o365_series_id', 'NEW'); + assert.ok(out.includes('o365_series_id: NEW')); + assert.ok(!out.includes('OLD')); + }); + + it('returns content unchanged when no frontmatter present', () => { + const md = `# No frontmatter\n`; + const out = upsertFrontmatterField(md, 'o365_series_id', 'X'); + assert.equal(out, md); + }); +});