Skip to content

Commit a5e395f

Browse files
Copilotpethers
andauthored
fix: address review-4180927536 — stderr logs, range filenames, --tom alias, CliArgsError
Agent-Logs-Url: https://github.com/Hack23/riksdagsmonitor/sessions/ed2d6569-2714-4ed0-9fc9-55bf7937ee66 Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
1 parent 40f4c34 commit a5e395f

2 files changed

Lines changed: 76 additions & 15 deletions

File tree

scripts/fetch-calendar.ts

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -665,10 +665,10 @@ export async function fetchCalendarWithFallback(
665665
}
666666

667667
try {
668-
console.log(` 🔄 [fetch-calendar] MCP primary attempt ${attempt + 1}/${maxRetries + 1}…`);
668+
console.error(` 🔄 [fetch-calendar] MCP primary attempt ${attempt + 1}/${maxRetries + 1}…`);
669669
const raw = await callMcpCalendarEvents(from, to, resolved);
670670
const events = raw.map(normalizeMcpCalendarEvent);
671-
console.log(` ✅ [fetch-calendar] MCP primary succeeded — ${events.length} events`);
671+
console.error(` ✅ [fetch-calendar] MCP primary succeeded — ${events.length} events`);
672672

673673
return {
674674
events,
@@ -691,11 +691,11 @@ export async function fetchCalendarWithFallback(
691691
}
692692

693693
// ── Web fallback path ──────────────────────────────────────────────────
694-
console.log(` 🔄 [fetch-calendar] Falling back to riksdagen.se/sv/kalendarium/…`);
694+
console.error(` 🔄 [fetch-calendar] Falling back to riksdagen.se/sv/kalendarium/…`);
695695
let fallbackError: string | undefined;
696696
try {
697697
const events = await fetchWebCalendar(from, to, resolved);
698-
console.log(` ✅ [fetch-calendar] Web fallback succeeded — ${events.length} events`);
698+
console.error(` ✅ [fetch-calendar] Web fallback succeeded — ${events.length} events`);
699699

700700
return {
701701
events,
@@ -737,25 +737,29 @@ const REPO_ROOT = path.resolve(__dirname, '..');
737737
const CALENDAR_DIR = path.join(REPO_ROOT, 'data', 'calendar');
738738

739739
/**
740-
* Write a `CalendarFetchResult` to `data/calendar/{from}.json`.
740+
* Write a `CalendarFetchResult` to `data/calendar/{from}_{to}.json`.
741741
*
742742
* The file is an object with `{ manifest, events }` so that consumers can
743743
* load a single file and get both the data and the provenance record.
744+
* Including `to` in the filename prevents collisions when the same `from`
745+
* date is fetched with different ranges (e.g. week-ahead vs month-ahead).
744746
*/
745747
export function persistCalendarJson(
746748
from: string,
747749
result: CalendarFetchResult,
748750
outputDir: string = CALENDAR_DIR,
749751
): string {
750752
fs.mkdirSync(outputDir, { recursive: true });
751-
const outputPath = path.join(outputDir, `${from}.json`);
753+
const dateTo = result.manifest.dateTo ?? from;
754+
const fileName = dateTo && dateTo !== from ? `${from}_${dateTo}.json` : `${from}.json`;
755+
const outputPath = path.join(outputDir, fileName);
752756
const payload = {
753757
schema: 'riksdagsmonitor-calendar/1.0',
754758
manifest: result.manifest,
755759
events: result.events,
756760
};
757761
fs.writeFileSync(outputPath, JSON.stringify(payload, null, 2), 'utf8');
758-
console.log(` 💾 [fetch-calendar] Persisted ${result.events.length} events → ${outputPath}`);
762+
console.error(` 💾 [fetch-calendar] Persisted ${result.events.length} events → ${outputPath}`);
759763
return outputPath;
760764
}
761765

@@ -791,7 +795,20 @@ export function formatManifestMarkdown(manifest: CalendarFetchManifest): string
791795
return lines.join('\n');
792796
}
793797

794-
/** Parse CLI argv into `{ from, to, persist }`. */
798+
/** Thrown by `parseCalendarArgs` for invalid CLI arguments (exit code 2). */
799+
export class CliArgsError extends Error {
800+
constructor(message: string) {
801+
super(message);
802+
this.name = 'CliArgsError';
803+
}
804+
}
805+
806+
/**
807+
* Parse CLI argv into `{ from, to, persist }`.
808+
*
809+
* Accepts `--to` (preferred) and `--tom` (Swedish alias, used in repo docs)
810+
* as the end-date flag. Throws `CliArgsError` for invalid arguments.
811+
*/
795812
export function parseCalendarArgs(argv: readonly string[]): {
796813
from: string;
797814
to: string;
@@ -813,29 +830,31 @@ export function parseCalendarArgs(argv: readonly string[]): {
813830
}
814831
const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
815832
const from = flags.get('from') ?? '';
816-
const to = flags.get('to') ?? '';
833+
// Accept both `--to` and `--tom` (Swedish alias used in repo docs).
834+
const to = flags.get('to') ?? flags.get('tom') ?? '';
817835
if (!ISO_DATE_RE.test(from)) {
818-
throw new Error(`--from must be an ISO 8601 date (YYYY-MM-DD), got: "${from}"`);
836+
throw new CliArgsError(`--from must be an ISO 8601 date (YYYY-MM-DD), got: "${from}"`);
819837
}
820838
if (!ISO_DATE_RE.test(to)) {
821-
throw new Error(`--to must be an ISO 8601 date (YYYY-MM-DD), got: "${to}"`);
839+
throw new CliArgsError(`--to must be an ISO 8601 date (YYYY-MM-DD), got: "${to}"`);
822840
}
823841
return { from, to, persist: booleans.has('persist') };
824842
}
825843

826844
async function main(): Promise<void> {
827845
const args = parseCalendarArgs(process.argv.slice(2));
828-
console.log(`📅 [fetch-calendar] Fetching ${args.from}${args.to}`);
846+
console.error(`📅 [fetch-calendar] Fetching ${args.from}${args.to}`);
829847

830848
const result = await fetchCalendarWithFallback(args.from, args.to);
831849

832-
console.log(formatManifestMarkdown(result.manifest));
850+
// Manifest is human-readable status info → stderr, not stdout.
851+
console.error(formatManifestMarkdown(result.manifest));
833852

834853
if (args.persist) {
835854
persistCalendarJson(args.from, result);
836855
} else {
837856
// Print JSON to stdout for piping / agentic workflow consumption.
838-
console.log(JSON.stringify(result, null, 2));
857+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
839858
}
840859

841860
if (result.manifest.path === 'none') {
@@ -847,6 +866,7 @@ async function main(): Promise<void> {
847866
if (path.resolve(fileURLToPath(import.meta.url)) === path.resolve(process.argv[1] ?? '')) {
848867
main().catch((err: unknown) => {
849868
console.error('❌ [fetch-calendar] Fatal error:', err instanceof Error ? err.message : err);
850-
process.exit(1);
869+
// Bad CLI arguments → exit code 2 (per module header & repo convention).
870+
process.exit(err instanceof CliArgsError ? 2 : 1);
851871
});
852872
}

tests/fetch-calendar.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
formatManifestMarkdown,
3636
parseCalendarArgs,
3737
persistCalendarJson,
38+
CliArgsError,
3839
type CalendarFetchConfig,
3940
type CalendarEvent,
4041
} from '../scripts/fetch-calendar.js';
@@ -722,6 +723,28 @@ describe('parseCalendarArgs', () => {
722723
parseCalendarArgs(['--from', '28-04-2026', '--to', '2026-05-04']),
723724
).toThrow(/ISO 8601/);
724725
});
726+
727+
it('accepts --tom as an alias for --to (Swedish, used in repo docs)', () => {
728+
const args = parseCalendarArgs(['--from', '2026-04-28', '--tom', '2026-05-04']);
729+
expect(args.from).toBe('2026-04-28');
730+
expect(args.to).toBe('2026-05-04');
731+
});
732+
733+
it('prefers --to over --tom when both are provided', () => {
734+
const args = parseCalendarArgs([
735+
'--from', '2026-04-28',
736+
'--to', '2026-05-04',
737+
'--tom', '2026-05-31',
738+
]);
739+
expect(args.to).toBe('2026-05-04');
740+
});
741+
742+
it('throws CliArgsError (typed) for invalid arguments', () => {
743+
expect(() => parseCalendarArgs(['--to', '2026-05-04'])).toThrow(CliArgsError);
744+
expect(() =>
745+
parseCalendarArgs(['--from', 'bogus', '--to', '2026-05-04']),
746+
).toThrow(CliArgsError);
747+
});
725748
});
726749

727750
// ---------------------------------------------------------------------------
@@ -945,4 +968,22 @@ describe('persistCalendarJson', () => {
945968
const outPath = persistCalendarJson('2026-05-01', result, outputDir);
946969
expect(outPath).toBe(path.join(outputDir, '2026-05-01.json'));
947970
});
971+
972+
it('uses {from}_{dateTo}.json when range spans multiple days', () => {
973+
const outputDir = path.join(tmpDir, 'calendar');
974+
const result = {
975+
manifest: {
976+
path: 'mcp-primary' as const,
977+
date: '2026-04-28',
978+
dateTo: '2026-05-04',
979+
eventCount: 0,
980+
fetchedAt: '2026-04-28T00:00:00.000Z',
981+
},
982+
events: [],
983+
};
984+
985+
const outPath = persistCalendarJson('2026-04-28', result, outputDir);
986+
expect(outPath).toBe(path.join(outputDir, '2026-04-28_2026-05-04.json'));
987+
expect(fs.existsSync(outPath)).toBe(true);
988+
});
948989
});

0 commit comments

Comments
 (0)