forked from GitHubMirrors/silverbullet-icalendar
Compare commits
14 Commits
ac2d1971be
...
335c859e65
| Author | SHA1 | Date | |
|---|---|---|---|
| 335c859e65 | |||
| 0f04df1435 | |||
| e8d74e4622 | |||
| 657f4f2c3a | |||
| ea85b56c5c | |||
| df8a0e12c2 | |||
| 9b53b77929 | |||
| cafdaf7006 | |||
| f8640533be | |||
| cecaac6638 | |||
| 54bb7a8540 | |||
| 6eda06aca6 | |||
| b5c718f286 | |||
| 0bea770814 |
@@ -6,3 +6,8 @@ This file tracks all major tracks for the project. Each track has its own detail
|
||||
|
||||
- [x] **Track: Upgrade the SilverBullet iCalendar plug to use DST-aware timezone resolution and add recurring event support using rrule.**
|
||||
*Link: [./tracks/timezone_rrule_20260218/](./tracks/timezone_rrule_20260218/)*
|
||||
|
||||
---
|
||||
|
||||
- [x] **Track: Fix TypeError: r.replace is not a function in icalendar.ts**
|
||||
*Link: [./tracks/fix_rrule_type_error_20260219/](./tracks/fix_rrule_type_error_20260219/)*
|
||||
|
||||
5
conductor/tracks/fix_rrule_type_error_20260219/index.md
Normal file
5
conductor/tracks/fix_rrule_type_error_20260219/index.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Track fix_rrule_type_error_20260219 Context
|
||||
|
||||
- [Specification](./spec.md)
|
||||
- [Implementation Plan](./plan.md)
|
||||
- [Metadata](./metadata.json)
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"track_id": "fix_rrule_type_error_20260219",
|
||||
"type": "bug",
|
||||
"status": "new",
|
||||
"created_at": "2026-02-19T00:00:00Z",
|
||||
"updated_at": "2026-02-19T00:00:00Z",
|
||||
"description": "Fix TypeError: r.replace is not a function in icalendar.ts"
|
||||
}
|
||||
20
conductor/tracks/fix_rrule_type_error_20260219/plan.md
Normal file
20
conductor/tracks/fix_rrule_type_error_20260219/plan.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Implementation Plan - Fix `r.replace is not a function`
|
||||
|
||||
## Phase 1: Reproduction & Test Setup [checkpoint: df0ddaf]
|
||||
- [x] Task: Create a reproduction test case 1a36c64
|
||||
- [x] Create a new test case in `icalendar_test.ts` that mocks an event with a non-string `rrule` property (e.g., an object or number).
|
||||
- [x] Run the test to confirm it fails with the expected `TypeError`.
|
||||
- [x] Task: Conductor - User Manual Verification 'Reproduction & Test Setup' (Protocol in workflow.md)
|
||||
|
||||
## Phase 2: Implementation [checkpoint: 1c48f78]
|
||||
- [x] Task: Implement defensive check in `icalendar.ts` d7401dd
|
||||
- [x] Modify `expandRecurrences` function to check if `rruleStr` is a string before calling `.replace()`.
|
||||
- [x] If `rruleStr` is not a string, log a warning and return the original event (non-recurring fallback).
|
||||
- [x] Run the reproduction test again to confirm it passes.
|
||||
- [x] Task: Conductor - User Manual Verification 'Implementation' (Protocol in workflow.md)
|
||||
|
||||
## Phase 3: Verification & Cleanup
|
||||
- [x] Task: Verify fix and check for regressions
|
||||
- [x] Run all tests in `icalendar_test.ts` to ensure existing functionality is preserved.
|
||||
- [x] (Optional) Verify with actual calendar sync if possible/safe.
|
||||
- [x] Task: Conductor - User Manual Verification 'Verification & Cleanup' (Protocol in workflow.md)
|
||||
25
conductor/tracks/fix_rrule_type_error_20260219/spec.md
Normal file
25
conductor/tracks/fix_rrule_type_error_20260219/spec.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Specification: Fix `r.replace is not a function` in `expandRecurrences`
|
||||
|
||||
## Overview
|
||||
This track addresses a `TypeError: r.replace is not a function` error occurring in `icalendar.ts` during calendar synchronization. The error suggests that the `rrule` property of an event is not a string when it reaches the `expandRecurrences` function, causing the subsequent `.replace()` call to fail. This is likely due to the `ts-ics` parser returning a non-string value (e.g., an object or `undefined`) for the `rrule` property in certain scenarios (specifically observed with Outlook calendars).
|
||||
|
||||
## Functional Requirements
|
||||
- **Defensive RRULE Handling:** The `expandRecurrences` function in `icalendar.ts` must safely handle cases where `rrule` (or `recurrenceRule`) is not a string.
|
||||
- **Graceful Fallback:** If `rrule` is not a string:
|
||||
- It should be ignored/logged if it cannot be interpreted as a valid RRULE string, preventing the crash.
|
||||
- The event should still be processed (treated as a non-recurring event if the rule is invalid), rather than crashing the entire sync for that event.
|
||||
|
||||
## Non-Functional Requirements
|
||||
- **Stability:** The plug should not crash or throw unhandled exceptions during sync due to malformed or unexpected property types in the source ICS data.
|
||||
- **Logging:** Maintain existing error logging but ensure the error message is descriptive (e.g., "Invalid RRULE type: object").
|
||||
|
||||
## Implementation Steps
|
||||
1. **Reproduce Issue:** Create a unit test in `icalendar_test.ts` that mocks an `icsEvent` with a non-string `rrule` property (e.g., an object or number) and calls `expandRecurrences`.
|
||||
2. **Implement Fix:** Modify `icalendar.ts` to check the type of `rruleStr` before calling `.replace()`.
|
||||
- If it's not a string, attempt to convert it or return the original event (as if no recurrence rule exists) with a warning.
|
||||
3. **Verify:** Run the new unit test to confirm the fix.
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ] A new unit test case exists in `icalendar_test.ts` that passes with a non-string `rrule`.
|
||||
- [ ] The `expandRecurrences` function no longer throws `TypeError: r.replace is not a function` when `rrule` is not a string.
|
||||
- [ ] The sync process completes successfully even if some events have malformed `rrule` properties.
|
||||
@@ -134,6 +134,24 @@ All tasks follow a strict lifecycle:
|
||||
|
||||
10. **Announce Completion:** Inform the user that the phase is complete and the checkpoint has been created, with the detailed verification report attached as a git note.
|
||||
|
||||
### Track Completion Protocol
|
||||
|
||||
**Trigger:** This protocol is executed when all phases and tasks in a track are complete.
|
||||
|
||||
1. **Version Bump (Code Changes Only):**
|
||||
- If the track involved code changes (i.e., not purely documentation), you **must** bump the project version number.
|
||||
- Update `deno.json` and any other version-tracked files (e.g., `icalendar.ts`) with the new version.
|
||||
- Commit the version bump with message: `chore: Bump version to <new_version>`.
|
||||
|
||||
2. **Push Changes (Code Changes Only):**
|
||||
- If the track involved code changes, you **must** push the changes to the remote repository.
|
||||
- **Command:** `git push`
|
||||
|
||||
3. **Monitor CI/CD (Code Changes Only):**
|
||||
- After pushing, you **must** monitor the resulting Gitea Action to ensure it completes successfully.
|
||||
- Use the `gitea-push-watch` skill if available, or check the Gitea interface manually.
|
||||
- **Requirement:** The track is NOT complete until the CI/CD pipeline passes. If it fails, you must investigate and fix the issue.
|
||||
|
||||
### Quality Gates
|
||||
|
||||
Before marking any task complete, verify:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "icalendar-plug",
|
||||
"version": "0.3.28",
|
||||
"version": "0.3.29",
|
||||
"nodeModulesDir": "auto",
|
||||
"tasks": {
|
||||
"sync-version": "deno run -A scripts/sync-version.ts",
|
||||
|
||||
@@ -3,7 +3,7 @@ import { convertIcsCalendar } from "https://esm.sh/ts-ics@2.4.0";
|
||||
import { RRule, RRuleSet } from "rrule";
|
||||
import { getUtcOffsetMs, resolveIanaName } from "./timezones.ts";
|
||||
|
||||
const VERSION = "0.3.28";
|
||||
const VERSION = "0.3.29";
|
||||
const CACHE_KEY = "icalendar:lastSync";
|
||||
|
||||
console.log(`[iCalendar] Plug script executing at top level (Version ${VERSION})`);
|
||||
@@ -154,6 +154,11 @@ export function expandRecurrences(icsEvent: any, windowDays = 365): any[] {
|
||||
const rruleStr = icsEvent.rrule || (icsEvent as any).recurrenceRule;
|
||||
if (!rruleStr) return [icsEvent];
|
||||
|
||||
if (typeof rruleStr !== "string") {
|
||||
console.warn(`[iCalendar] Invalid rrule type (${typeof rruleStr}) for event "${icsEvent.summary || "Untitled"}". Treating as non-recurring.`);
|
||||
return [icsEvent];
|
||||
}
|
||||
|
||||
try {
|
||||
const set = new RRuleSet();
|
||||
const cleanRule = rruleStr.replace(/^RRULE:/i, "");
|
||||
|
||||
@@ -121,4 +121,36 @@ Deno.test("expandRecurrences - custom windowDays", () => {
|
||||
// It should include everything in the last 7 days + next 2 days.
|
||||
// Since it's daily, that's roughly 7 + 2 + 1 = 10 events.
|
||||
assert(results.length >= 3, "Should have at least today and 2 future days");
|
||||
});
|
||||
});
|
||||
Deno.test("expandRecurrences - non-string rrule (Reproduction)", () => {
|
||||
const now = new Date();
|
||||
const startStr = localDateString(now);
|
||||
|
||||
const icsEvent = {
|
||||
summary: "Bug Reproduction Event",
|
||||
start: startStr,
|
||||
rrule: 12345 // Simulating the malformed data
|
||||
};
|
||||
|
||||
// Spy on console.warn
|
||||
let warningLogged = false;
|
||||
const originalConsoleWarn = console.warn;
|
||||
console.warn = (...args) => {
|
||||
if (args[0].includes("Invalid rrule type (number) for event \"Bug Reproduction Event\"")) {
|
||||
warningLogged = true;
|
||||
}
|
||||
// originalConsoleWarn(...args); // Keep silent for test
|
||||
};
|
||||
|
||||
try {
|
||||
const result = expandRecurrences(icsEvent, 30);
|
||||
// Should return the original event as fallback
|
||||
assertEquals(result.length, 1);
|
||||
assertEquals(result[0], icsEvent);
|
||||
} finally {
|
||||
console.warn = originalConsoleWarn;
|
||||
}
|
||||
|
||||
assert(warningLogged, "Should have logged a warning for non-string rrule");
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user