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:
parent
15499cf190
commit
0679aa013f
|
|
@ -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 };
|
||||||
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue