feat(scripts): add backfill-series-id script for jour-fixe agendas

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
beo3000 2026-05-06 21:08:08 +02:00
parent 15499cf190
commit 0679aa013f
2 changed files with 82 additions and 0 deletions

View File

@ -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 <agenda-path> <title-regex>');
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 };

View File

@ -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);
});
});