kennel 0.2.2 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -13
- package/dist/cli/dispatch.js +16 -3
- package/dist/cli/src/cli/dispatch.js +16 -3
- package/dist/cli/src/cli/usage.js +1 -1
- package/dist/cli/src/core/command.js +1 -0
- package/dist/cli/src/core/init.js +5 -5
- package/dist/cli/src/plugins/external.d.ts +29 -3
- package/dist/cli/src/plugins/external.js +78 -24
- package/dist/cli/src/site/server-plugins.d.ts +2 -7
- package/dist/cli/src/site/viewer.js +1 -0
- package/dist/cli/usage.js +1 -1
- package/dist/core/command.js +1 -0
- package/dist/core/init.js +5 -5
- package/dist/core/runtime.js +357 -3207
- package/dist/package/preview.js +1232 -5113
- package/dist/plugins/external.d.ts +29 -3
- package/dist/plugins/external.js +78 -24
- package/dist/site/server-plugins.d.ts +2 -7
- package/dist/site/server-plugins.js +552 -10447
- package/dist/site/viewer.js +1 -0
- package/kernel/apps/web/package.json +1 -5
- package/kernel/apps/web/scripts/dev.mjs +53 -0
- package/kernel/apps/web/src/app.css +0 -3
- package/kernel/apps/web/src/main.tsx +0 -1
- package/kernel/apps/web/src/plugins.ts +0 -30
- package/kernel/apps/web/tsconfig.json +1 -2
- package/kernel/apps/web/vite.config.ts +31 -13
- package/kernel/packages/api/package.json +0 -4
- package/kernel/packages/api/src/server/folder-metadata-plugin.ts +13 -38
- package/kernel/packages/api/src/server/index.ts +0 -1
- package/kernel/packages/api/src/server/plugins.ts +49 -16
- package/kernel/packages/api/test/core.test.ts +13 -25
- package/kernel/packages/markdown/README.md +1 -1
- package/package.json +1 -1
- package/web/assets/{abnfDiagram-VRR7QNED-Bgfb1nEB.js → abnfDiagram-VRR7QNED-xd_g1uK3.js} +1 -1
- package/web/assets/app-CX20nNIa.js +2 -0
- package/web/assets/{app-options-D-RJiQ-X.js → app-options-CkPJE5ep.js} +1 -1
- package/web/assets/{app-view-header-BLvLq02T.js → app-view-header-CNk5KnA-.js} +1 -1
- package/web/assets/{arc-Dk_KI36B.js → arc-B0u5yBJA.js} +1 -1
- package/web/assets/{architectureDiagram-ZJ3FMSHR-NR_KhRCf.js → architectureDiagram-ZJ3FMSHR-DfxOAo8P.js} +1 -1
- package/web/assets/{ask-tray-TzeTcO6-.js → ask-tray-SscJecDY.js} +1 -1
- package/web/assets/{blockDiagram-677ZJIJ3-CkLkwx9C.js → blockDiagram-677ZJIJ3-Ci3Usxh6.js} +1 -1
- package/web/assets/{c4Diagram-LMCZKHZV-CFCUH8e_.js → c4Diagram-LMCZKHZV-BqgwIVgh.js} +1 -1
- package/web/assets/channel-X7WHyxxT.js +1 -0
- package/web/assets/{chunk-2Q5K7J3B-B_BPLWsW.js → chunk-2Q5K7J3B-CfX69l2U.js} +1 -1
- package/web/assets/{chunk-32BRIVSS-B1VB1hOy.js → chunk-32BRIVSS-CuFlzZkl.js} +1 -1
- package/web/assets/{chunk-5VM5RSS4-KFcYr8Co.js → chunk-5VM5RSS4-DidCMlJa.js} +1 -1
- package/web/assets/{chunk-EX3LRPZG-CpBX3kuK.js → chunk-EX3LRPZG-B5CDm1dS.js} +1 -1
- package/web/assets/{chunk-JWPE2WC7-BPSl40wG.js → chunk-JWPE2WC7-D5fGV_sH.js} +1 -1
- package/web/assets/{chunk-MOJQB5TN-D2RcYmoU.js → chunk-MOJQB5TN-ayhH5Q-H.js} +1 -1
- package/web/assets/{chunk-RYQCIY6F-m7Gyn-t3.js → chunk-RYQCIY6F-C4BfOOZn.js} +1 -1
- package/web/assets/{chunk-V7JOEXUC-TDvdopxS.js → chunk-V7JOEXUC-DpUEIM9V.js} +1 -1
- package/web/assets/{chunk-VR4S4FIN-DZTsG51a.js → chunk-VR4S4FIN-DoQuFFRh.js} +1 -1
- package/web/assets/{chunk-XXDRQBXY-Dp0--RE2.js → chunk-XXDRQBXY-CI9eiLwn.js} +1 -1
- package/web/assets/classDiagram-OUVF2IWQ-BjsLQ3dz.js +1 -0
- package/web/assets/classDiagram-v2-EOCWNBFH-BjsLQ3dz.js +1 -0
- package/web/assets/client-7OWw5kW2.js +6 -0
- package/web/assets/client-DYj_td4Q.js +17 -0
- package/web/assets/{client-j2kQmxsO.js → client-Dk-NBvJR.js} +4 -4
- package/web/assets/command-zkzH4hVz.js +38 -0
- package/web/assets/{copy-context-C0Uqsp-3.js → copy-context-D5oiWrYC.js} +1 -1
- package/web/assets/{cose-bilkent-JH36ORCC-CPu3PI8h.js → cose-bilkent-JH36ORCC-BHRk1mO3.js} +1 -1
- package/web/assets/{cynefin-VYW2F7L2-DWxftYAm.js → cynefin-VYW2F7L2-C1B09TRE.js} +1 -1
- package/web/assets/{cynefinDiagram-TSTJHNR4-DsmN2V-p.js → cynefinDiagram-TSTJHNR4-B27qXQbv.js} +1 -1
- package/web/assets/{dagre-VKFMJZFB-COEbApyY.js → dagre-VKFMJZFB-B1qEi32g.js} +1 -1
- package/web/assets/{diagram-FQU43EPY-0O9WGWKB.js → diagram-FQU43EPY-BMkkgADD.js} +1 -1
- package/web/assets/{diagram-G47NLZAW-CutEdMVh.js → diagram-G47NLZAW-Cb8_g1A5.js} +1 -1
- package/web/assets/{diagram-NH7WQ7WH-C9KZc3B2.js → diagram-NH7WQ7WH-B51wuc5m.js} +1 -1
- package/web/assets/{diagram-OA4YK3LP-D8kkPXbY.js → diagram-OA4YK3LP-xnsecbbI.js} +1 -1
- package/web/assets/{diagram-WEI45ONY-_YqiTaGP.js → diagram-WEI45ONY-DSZdyaAV.js} +1 -1
- package/web/assets/{document-Dgt5eZFj.js → document-Dc2dftTW.js} +3 -3
- package/web/assets/{ebnfDiagram-CCIWWBDH-JO-zQh6g.js → ebnfDiagram-CCIWWBDH-aTHmcUGy.js} +1 -1
- package/web/assets/{entities-C3wz7FCx.js → entities-CCUtpskY.js} +1 -1
- package/web/assets/{erDiagram-Q63AITRT-9UjQwJ4P.js → erDiagram-Q63AITRT-CUUPBcvo.js} +1 -1
- package/web/assets/{file-viewer-DZJ8nYnl.js → file-viewer-DJ3rsmef.js} +1 -1
- package/web/assets/{flowDiagram-23GEKE2U-Ca08tnYM.js → flowDiagram-23GEKE2U-aTQXx2Mh.js} +1 -1
- package/web/assets/{folder-image-BfGgxo7i.js → folder-image-Bnv43tzP.js} +1 -1
- package/web/assets/{folder-landing-qUKBCnG-.js → folder-landing-DCnzYXXY.js} +1 -1
- package/web/assets/{folder-list-BfFjv7rR.js → folder-list-DvVkVjiR.js} +1 -1
- package/web/assets/{folder-metadata-DCw8xzHk.js → folder-metadata-C9hMMzIH.js} +1 -1
- package/web/assets/{folder-mobile-bar-Ubz4babf.js → folder-mobile-bar-VoTL1GS0.js} +1 -1
- package/web/assets/folder-plugin-view-Cvd804ZU.js +6 -0
- package/web/assets/{ganttDiagram-NO4QXBWP-KnHzX5W3.js → ganttDiagram-NO4QXBWP-DiwdUp7u.js} +1 -1
- package/web/assets/{gitGraphDiagram-IHSO6WYX-Cm8woNtK.js → gitGraphDiagram-IHSO6WYX-BL2gG_IS.js} +1 -1
- package/web/assets/{index-CNyy98F8.js → index-CUTtMSyJ.js} +2 -2
- package/web/assets/index-CegvVQ1C.js +1 -0
- package/web/assets/index-Dwhf8F_U.css +1 -0
- package/web/assets/{infoDiagram-FWYZ7A6U-yyqmUtIb.js → infoDiagram-FWYZ7A6U-B3n0zZpt.js} +1 -1
- package/web/assets/{ishikawaDiagram-FXEZZL3T-BfvR_Zy4.js → ishikawaDiagram-FXEZZL3T-D7YytbEv.js} +1 -1
- package/web/assets/{journeyDiagram-5HDEW3XC-BKuw06HM.js → journeyDiagram-5HDEW3XC-BYwqEl3C.js} +1 -1
- package/web/assets/{kanban-definition-HUTT4EX6-n0010rmQ.js → kanban-definition-HUTT4EX6-BQHNJgXZ.js} +1 -1
- package/web/assets/{linear-C0d0lT8O.js → linear-BPiQCLNk.js} +1 -1
- package/web/assets/{mermaid.core-BDVbShyO.js → mermaid.core-CD-KvBjV.js} +4 -4
- package/web/assets/{metadata-KV0dpp4G.js → metadata-COJ7fZul.js} +1 -1
- package/web/assets/{mindmap-definition-LN4V7U3C--_0ks3GF.js → mindmap-definition-LN4V7U3C-C1L_gyoj.js} +1 -1
- package/web/assets/{pegDiagram-2B236MQR-CBoNIpKR.js → pegDiagram-2B236MQR-BDAfItqS.js} +1 -1
- package/web/assets/{pieDiagram-ENE6RG2P-MA5tkYh5.js → pieDiagram-ENE6RG2P-CkZYH3Vz.js} +1 -1
- package/web/assets/{popover-DBD7FF-W.js → popover-0oA2lLUV.js} +2 -2
- package/web/assets/{project-search-dialog-DM1IkSZu.js → project-search-dialog-B2oqMT6h.js} +1 -1
- package/web/assets/{quadrantDiagram-ABIIQ3AL-CWT1ryqF.js → quadrantDiagram-ABIIQ3AL-Dy8Al3YZ.js} +1 -1
- package/web/assets/{railroadDiagram-RFXS5EU6-ZiLB5FGi.js → railroadDiagram-RFXS5EU6-Dd9_yo7B.js} +1 -1
- package/web/assets/{requirementDiagram-TGXJPOKE-CQq9uneq.js → requirementDiagram-TGXJPOKE-DPhIUsbs.js} +1 -1
- package/web/assets/{sankeyDiagram-HTMAVEWB-qDG-0JqI.js → sankeyDiagram-HTMAVEWB-B2QkYtub.js} +1 -1
- package/web/assets/{search-DNrQA5fF.js → search-C1J0-xiB.js} +1 -1
- package/web/assets/select-CtXC_PLC.js +30 -0
- package/web/assets/{selection-popup-DmtTeRs2.js → selection-popup-qhlUFzGq.js} +1 -1
- package/web/assets/{sequenceDiagram-DBY2YBRQ-CiApurLO.js → sequenceDiagram-DBY2YBRQ-Bx7fcZDx.js} +1 -1
- package/web/assets/{sizeCapture-X5ZJPWSS-DZSR5QXm.js → sizeCapture-X5ZJPWSS-CIXIhl_A.js} +1 -1
- package/web/assets/{stateDiagram-2N3HPSRC-CjJJR9_z.js → stateDiagram-2N3HPSRC-DCrdAflB.js} +1 -1
- package/web/assets/stateDiagram-v2-6OUMAXLB-0KCFLYeQ.js +1 -0
- package/web/assets/{swimlanes-5IMT3BWC-CKmo1sg6.js → swimlanes-5IMT3BWC-DA2vc7iZ.js} +2 -2
- package/web/assets/swimlanesDiagram-G3AALYLV-CAw3AV84.js +8 -0
- package/web/assets/{timeline-definition-FHXFAJF6-D-NMwS3y.js → timeline-definition-FHXFAJF6-lKfJiV9V.js} +1 -1
- package/web/assets/{vennDiagram-L72KCM5P-CfL6LBWw.js → vennDiagram-L72KCM5P-DO1q2KzG.js} +1 -1
- package/web/assets/{view-context-gyDl_p2V.js → view-context-DXKM3-l1.js} +4 -4
- package/web/assets/{viewer-capabilities-Bpp8Uvcg.js → viewer-capabilities-Cig_xM9u.js} +1 -1
- package/web/assets/{wardleyDiagram-EHGQE667-DgldMxaj.js → wardleyDiagram-EHGQE667-Dal9LkuZ.js} +1 -1
- package/web/assets/{xychartDiagram-FW5EYKEG-CIoAIFX8.js → xychartDiagram-FW5EYKEG-B3dPLPII.js} +1 -1
- package/web/index.html +2 -2
- package/kernel/packages/api/test/partial-loading.test.ts +0 -43
- package/kernel/plugins/calendar/SKILL.md +0 -173
- package/kernel/plugins/calendar/eslint.config.js +0 -10
- package/kernel/plugins/calendar/package.json +0 -73
- package/kernel/plugins/calendar/schema.json +0 -137
- package/kernel/plugins/calendar/src/calendar.tsx +0 -328
- package/kernel/plugins/calendar/src/client.tsx +0 -139
- package/kernel/plugins/calendar/src/context.ts +0 -48
- package/kernel/plugins/calendar/src/copy-button.tsx +0 -66
- package/kernel/plugins/calendar/src/event-dialog.tsx +0 -270
- package/kernel/plugins/calendar/src/index.ts +0 -4
- package/kernel/plugins/calendar/src/manifest.ts +0 -7
- package/kernel/plugins/calendar/src/parse.ts +0 -610
- package/kernel/plugins/calendar/src/server.ts +0 -160
- package/kernel/plugins/calendar/src/utils.ts +0 -80
- package/kernel/plugins/calendar/src/views.tsx +0 -418
- package/kernel/plugins/calendar/test/context.test.mjs +0 -60
- package/kernel/plugins/calendar/test/package.test.mjs +0 -43
- package/kernel/plugins/calendar/test/parse.test.mjs +0 -392
- package/kernel/plugins/calendar/test/server.test.mjs +0 -126
- package/kernel/plugins/calendar/tsconfig.json +0 -17
- package/kernel/plugins/mail/SKILL.md +0 -97
- package/kernel/plugins/mail/eslint.config.js +0 -10
- package/kernel/plugins/mail/package.json +0 -79
- package/kernel/plugins/mail/src/api.ts +0 -359
- package/kernel/plugins/mail/src/bun-ffi.d.ts +0 -14
- package/kernel/plugins/mail/src/client.tsx +0 -14
- package/kernel/plugins/mail/src/flock.ts +0 -137
- package/kernel/plugins/mail/src/folders.ts +0 -37
- package/kernel/plugins/mail/src/history.ts +0 -485
- package/kernel/plugins/mail/src/index.ts +0 -3
- package/kernel/plugins/mail/src/mail-card.tsx +0 -123
- package/kernel/plugins/mail/src/mail-conversation.tsx +0 -153
- package/kernel/plugins/mail/src/mail-detail-page.tsx +0 -467
- package/kernel/plugins/mail/src/mail-hooks.ts +0 -252
- package/kernel/plugins/mail/src/mail-page.tsx +0 -109
- package/kernel/plugins/mail/src/mail-request.ts +0 -78
- package/kernel/plugins/mail/src/mail-send-box.tsx +0 -175
- package/kernel/plugins/mail/src/mailos.ts +0 -723
- package/kernel/plugins/mail/src/manifest.ts +0 -7
- package/kernel/plugins/mail/src/node.ts +0 -171
- package/kernel/plugins/mail/src/parse.ts +0 -139
- package/kernel/plugins/mail/src/server.ts +0 -125
- package/kernel/plugins/mail/src/types.ts +0 -10
- package/kernel/plugins/mail/src/viewer.tsx +0 -147
- package/kernel/plugins/mail/test/flock.test.mjs +0 -156
- package/kernel/plugins/mail/test/folders.test.mjs +0 -138
- package/kernel/plugins/mail/test/history.test.mjs +0 -460
- package/kernel/plugins/mail/test/mailos.test.mjs +0 -752
- package/kernel/plugins/mail/test/node.test.mjs +0 -207
- package/kernel/plugins/mail/test/package.test.mjs +0 -54
- package/kernel/plugins/mail/test/parse.test.mjs +0 -32
- package/kernel/plugins/mail/test/server.test.mjs +0 -116
- package/kernel/plugins/mail/test/viewer.test.mjs +0 -192
- package/kernel/plugins/mail/tsconfig.json +0 -17
- package/kernel/plugins/projects/SKILL.md +0 -73
- package/kernel/plugins/projects/eslint.config.js +0 -10
- package/kernel/plugins/projects/package.json +0 -70
- package/kernel/plugins/projects/src/client.tsx +0 -17
- package/kernel/plugins/projects/src/data/model.ts +0 -129
- package/kernel/plugins/projects/src/index.ts +0 -17
- package/kernel/plugins/projects/src/manifest.ts +0 -7
- package/kernel/plugins/projects/src/project-context.ts +0 -41
- package/kernel/plugins/projects/src/server.ts +0 -276
- package/kernel/plugins/projects/src/ui/project-card.tsx +0 -61
- package/kernel/plugins/projects/src/ui/projects-page.tsx +0 -102
- package/kernel/plugins/projects/test/package.test.mjs +0 -51
- package/kernel/plugins/projects/tsconfig.json +0 -17
- package/kernel/plugins/tasks/SKILL.md +0 -341
- package/kernel/plugins/tasks/eslint.config.js +0 -10
- package/kernel/plugins/tasks/package.json +0 -74
- package/kernel/plugins/tasks/schema.json +0 -939
- package/kernel/plugins/tasks/src/api.ts +0 -212
- package/kernel/plugins/tasks/src/client.tsx +0 -23
- package/kernel/plugins/tasks/src/data/parse.ts +0 -544
- package/kernel/plugins/tasks/src/data/task-id.ts +0 -56
- package/kernel/plugins/tasks/src/data/todo-model.ts +0 -795
- package/kernel/plugins/tasks/src/index.ts +0 -34
- package/kernel/plugins/tasks/src/manifest.ts +0 -7
- package/kernel/plugins/tasks/src/pages/task-detail.tsx +0 -48
- package/kernel/plugins/tasks/src/pages/task-list.tsx +0 -88
- package/kernel/plugins/tasks/src/server.ts +0 -447
- package/kernel/plugins/tasks/src/styles.css +0 -9
- package/kernel/plugins/tasks/src/ui/compact-todo-list.tsx +0 -372
- package/kernel/plugins/tasks/src/ui/error-details.tsx +0 -78
- package/kernel/plugins/tasks/src/ui/task-page.tsx +0 -392
- package/kernel/plugins/tasks/src/ui/todo-agent-filter.tsx +0 -36
- package/kernel/plugins/tasks/src/ui/todo-filter-bar.tsx +0 -66
- package/kernel/plugins/tasks/src/ui/todo-item.tsx +0 -494
- package/kernel/plugins/tasks/src/ui/todo-status-filter.tsx +0 -60
- package/kernel/plugins/tasks/src/ui/todo-tag-filter.tsx +0 -48
- package/kernel/plugins/tasks/src/ui/todo-warnings.tsx +0 -106
- package/kernel/plugins/tasks/test/api.test.mjs +0 -99
- package/kernel/plugins/tasks/test/folder-sources.test.mjs +0 -46
- package/kernel/plugins/tasks/test/package.test.mjs +0 -54
- package/kernel/plugins/tasks/test/parse.test.mjs +0 -225
- package/kernel/plugins/tasks/test/server.test.mjs +0 -386
- package/kernel/plugins/tasks/tsconfig.json +0 -17
- package/web/assets/app-DoGr2Zi7.js +0 -2
- package/web/assets/channel-uMEjV8qc.js +0 -1
- package/web/assets/classDiagram-OUVF2IWQ-fbZocVor.js +0 -1
- package/web/assets/classDiagram-v2-EOCWNBFH-fbZocVor.js +0 -1
- package/web/assets/client-C6Nxdfif.js +0 -2
- package/web/assets/client-CXYfBko5.js +0 -17
- package/web/assets/client-CoKtOq-X.js +0 -7
- package/web/assets/client-DyRQz5RU.js +0 -53
- package/web/assets/client-OY6HUzuD.js +0 -98
- package/web/assets/client-YOYWZIFi.js +0 -34
- package/web/assets/command-BugZBGXE.js +0 -38
- package/web/assets/folder-plugin-view-DsWR1zmQ.js +0 -6
- package/web/assets/index-BaRnbYRw.js +0 -1
- package/web/assets/index-BexmLxIP.css +0 -1
- package/web/assets/index-DDirZMgx.js +0 -1
- package/web/assets/note-card-DBfX6gW1.js +0 -6
- package/web/assets/select-D8cqNgcp.js +0 -30
- package/web/assets/stateDiagram-v2-6OUMAXLB-BpeGSCu9.js +0 -1
- package/web/assets/swimlanesDiagram-G3AALYLV-D9RjLKb9.js +0 -8
- package/web/assets/use-folder-filter-LQyXns5Q.js +0 -1
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict"
|
|
2
|
-
import test from "node:test"
|
|
3
|
-
|
|
4
|
-
import { dayContext, eventContext } from "../src/context.ts"
|
|
5
|
-
|
|
6
|
-
const event = {
|
|
7
|
-
id: "daily:2026-09-16",
|
|
8
|
-
title: "Daily Operations Check-in",
|
|
9
|
-
start: new Date("2026-09-16T08:30:00Z"),
|
|
10
|
-
end: new Date("2026-09-16T08:45:00Z"),
|
|
11
|
-
allDay: false,
|
|
12
|
-
attendees: ["the maintainer", "Operations team"],
|
|
13
|
-
color: "blue",
|
|
14
|
-
description: "Review the important tasks for today.",
|
|
15
|
-
excludeDates: [new Date("2026-09-20T00:00:00Z")],
|
|
16
|
-
group: "Operations",
|
|
17
|
-
location: "Operations room",
|
|
18
|
-
organizer: "Example Workspace",
|
|
19
|
-
reminders: [0, 15],
|
|
20
|
-
repeating: "daily",
|
|
21
|
-
repeatUntil: new Date("2026-12-31T23:59:59Z"),
|
|
22
|
-
status: "confirmed",
|
|
23
|
-
tags: ["operations", "daily"],
|
|
24
|
-
url: "https://example.com/example-workspace/check-in",
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
test("eventContext includes all event data for AI context", () => {
|
|
28
|
-
const context = eventContext(event)
|
|
29
|
-
|
|
30
|
-
for (const value of [
|
|
31
|
-
"Daily Operations Check-in",
|
|
32
|
-
"daily:2026-09-16",
|
|
33
|
-
"2026-09-16T08:30:00.000Z",
|
|
34
|
-
"All day: No",
|
|
35
|
-
"Group: Operations",
|
|
36
|
-
"Status: confirmed",
|
|
37
|
-
"Operations room",
|
|
38
|
-
"Example Workspace",
|
|
39
|
-
"the maintainer, Operations team",
|
|
40
|
-
"Repeats: daily",
|
|
41
|
-
"2026-09-20T00:00:00.000Z",
|
|
42
|
-
"at start, 15 minutes before",
|
|
43
|
-
"operations, daily",
|
|
44
|
-
"Color: blue",
|
|
45
|
-
"https://example.com/example-workspace/check-in",
|
|
46
|
-
"Review the important tasks for today.",
|
|
47
|
-
]) {
|
|
48
|
-
assert.ok(context.includes(value), `missing context value: ${value}`)
|
|
49
|
-
}
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
test("dayContext includes the selected date and its events", () => {
|
|
53
|
-
const context = dayContext(new Date("2026-09-16T12:00:00Z"), [event])
|
|
54
|
-
assert.match(context, /^# Calendar context for /)
|
|
55
|
-
assert.match(context, /Daily Operations Check-in/)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
test("dayContext explains an empty day", () => {
|
|
59
|
-
assert.match(dayContext(new Date("2026-09-18T12:00:00Z"), []), /No events\.$/)
|
|
60
|
-
})
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict"
|
|
2
|
-
import { readFile } from "node:fs/promises"
|
|
3
|
-
import test from "node:test"
|
|
4
|
-
|
|
5
|
-
const packageJson = JSON.parse(
|
|
6
|
-
await readFile(new URL("../package.json", import.meta.url), "utf8"),
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
test("calendar package is ready for public npm publishing", () => {
|
|
10
|
-
assert.equal(packageJson.private, undefined)
|
|
11
|
-
assert.equal(packageJson.version, "0.1.0")
|
|
12
|
-
assert.equal(packageJson.license, "MIT")
|
|
13
|
-
assert.equal(packageJson.publishConfig?.access, "public")
|
|
14
|
-
assert.equal(packageJson.repository?.directory, "plugins/calendar")
|
|
15
|
-
assert.deepEqual(packageJson.files, ["src"])
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
test("calendar package exposes parser, component, manifest, and split server/client entry points", () => {
|
|
19
|
-
assert.deepEqual(Object.keys(packageJson.exports).sort(), [
|
|
20
|
-
".",
|
|
21
|
-
"./calendar",
|
|
22
|
-
"./client",
|
|
23
|
-
"./context",
|
|
24
|
-
"./manifest",
|
|
25
|
-
"./parse",
|
|
26
|
-
"./server",
|
|
27
|
-
])
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
test("calendar client entry does not import the Node server entry", async () => {
|
|
31
|
-
const clientSource = await readFile(
|
|
32
|
-
new URL("../src/client.tsx", import.meta.url),
|
|
33
|
-
"utf8",
|
|
34
|
-
)
|
|
35
|
-
assert.doesNotMatch(clientSource, /node:|\/server"/)
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
test("calendar package uses the shared ui folder filter", () => {
|
|
39
|
-
const dependencyNames = Object.keys(packageJson.dependencies ?? {})
|
|
40
|
-
// The calendar view filters merged nested calendars by folder via the
|
|
41
|
-
// shared `@raggle-ai/ui` folder filter; its issue panel stays local.
|
|
42
|
-
assert.ok(dependencyNames.includes("@raggle-ai/ui"))
|
|
43
|
-
})
|
|
@@ -1,392 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict"
|
|
2
|
-
import { readFile } from "node:fs/promises"
|
|
3
|
-
import test from "node:test"
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
calendarIssueSummary,
|
|
7
|
-
eventCount,
|
|
8
|
-
eventsInRange,
|
|
9
|
-
mergeCalendars,
|
|
10
|
-
normalizeEvent,
|
|
11
|
-
parseCalendar,
|
|
12
|
-
parseCalendarJson,
|
|
13
|
-
parseCalendarSource,
|
|
14
|
-
parseEvents,
|
|
15
|
-
starterCalendarSource,
|
|
16
|
-
validateCalendarFile,
|
|
17
|
-
} from "../src/parse.ts"
|
|
18
|
-
|
|
19
|
-
const schema = JSON.parse(
|
|
20
|
-
await readFile(new URL("../schema.json", import.meta.url), "utf8"),
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
test("calendar schema publishes the repeating values", () => {
|
|
24
|
-
assert.equal(schema.$id, "https://raggle.co/schemas/calendar.json")
|
|
25
|
-
assert.deepEqual(schema.$defs.event.properties.repeating.enum, [
|
|
26
|
-
"daily",
|
|
27
|
-
"weekly",
|
|
28
|
-
"monthly",
|
|
29
|
-
])
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
test("calendar schema supports document and event groups", () => {
|
|
33
|
-
assert.equal(schema.properties.group.type, "string")
|
|
34
|
-
assert.deepEqual(schema.properties.color.enum, [
|
|
35
|
-
"blue",
|
|
36
|
-
"green",
|
|
37
|
-
"red",
|
|
38
|
-
"yellow",
|
|
39
|
-
"purple",
|
|
40
|
-
"orange",
|
|
41
|
-
])
|
|
42
|
-
assert.equal(schema.$defs.event.properties.group.type, "string")
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
test("calendar schema publishes ten enhanced event fields", () => {
|
|
46
|
-
const properties = schema.$defs.event.properties
|
|
47
|
-
assert.deepEqual(
|
|
48
|
-
[
|
|
49
|
-
"allDay",
|
|
50
|
-
"attendees",
|
|
51
|
-
"excludeDates",
|
|
52
|
-
"location",
|
|
53
|
-
"organizer",
|
|
54
|
-
"reminders",
|
|
55
|
-
"repeatUntil",
|
|
56
|
-
"status",
|
|
57
|
-
"tags",
|
|
58
|
-
"url",
|
|
59
|
-
].filter((field) => properties[field]),
|
|
60
|
-
[
|
|
61
|
-
"allDay",
|
|
62
|
-
"attendees",
|
|
63
|
-
"excludeDates",
|
|
64
|
-
"location",
|
|
65
|
-
"organizer",
|
|
66
|
-
"reminders",
|
|
67
|
-
"repeatUntil",
|
|
68
|
-
"status",
|
|
69
|
-
"tags",
|
|
70
|
-
"url",
|
|
71
|
-
],
|
|
72
|
-
)
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
test("parseCalendar accepts a top-level event array", () => {
|
|
76
|
-
const document = parseCalendar([
|
|
77
|
-
{
|
|
78
|
-
title: "Standup",
|
|
79
|
-
start: "2026-09-10T09:00:00",
|
|
80
|
-
end: "2026-09-10T09:30:00",
|
|
81
|
-
},
|
|
82
|
-
])
|
|
83
|
-
assert.equal(eventCount(document), 1)
|
|
84
|
-
assert.equal(document?.events[0]?.title, "Standup")
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
test("parseCalendar accepts an object with an events key and title", () => {
|
|
88
|
-
const document = parseCalendar({
|
|
89
|
-
title: "Example Workspace",
|
|
90
|
-
events: [{ title: "VAT Return", date: "2026-09-16T00:00:00" }],
|
|
91
|
-
})
|
|
92
|
-
assert.equal(document?.title, "Example Workspace")
|
|
93
|
-
assert.equal(document?.events[0]?.title, "VAT Return")
|
|
94
|
-
assert.ok(document?.events[0]?.start instanceof Date)
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
test("normalizeEvent fills a missing end time with a one-hour default", () => {
|
|
98
|
-
const event = normalizeEvent(
|
|
99
|
-
{ title: "Check-in", start: "2026-09-10T10:00:00" },
|
|
100
|
-
0,
|
|
101
|
-
)
|
|
102
|
-
assert.equal(event.end.getTime() - event.start.getTime(), 3_600_000)
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
test("normalizeEvent maps startDate and endDate variants", () => {
|
|
106
|
-
const event = normalizeEvent(
|
|
107
|
-
{ title: "Review", startDate: "2026-09-11", endDate: "2026-09-12" },
|
|
108
|
-
1,
|
|
109
|
-
)
|
|
110
|
-
assert.equal(event.title, "Review")
|
|
111
|
-
assert.ok(event.start instanceof Date)
|
|
112
|
-
assert.ok(event.end > event.start)
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
test("normalizeEvent keeps non-date events valid", () => {
|
|
116
|
-
const events = parseEvents([{ title: "No dates here" }])
|
|
117
|
-
assert.equal(events[0]?.title, "No dates here")
|
|
118
|
-
assert.ok(events[0]?.start instanceof Date)
|
|
119
|
-
assert.ok(events[0]?.end instanceof Date)
|
|
120
|
-
assert.ok(events[0]?.end > events[0]?.start)
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
test("parseEvents restores Date objects from serialized metadata", () => {
|
|
124
|
-
const serialized = JSON.parse(
|
|
125
|
-
JSON.stringify([
|
|
126
|
-
{ title: "VAT Return", start: new Date("2026-09-16T00:00:00") },
|
|
127
|
-
]),
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
const [event] = parseEvents(serialized)
|
|
131
|
-
assert.ok(event)
|
|
132
|
-
assert.equal(event.title, "VAT Return")
|
|
133
|
-
assert.ok(event.start instanceof Date, "start must be a Date, not a string")
|
|
134
|
-
assert.ok(event.end instanceof Date, "end must be a Date, not a string")
|
|
135
|
-
assert.equal(event.start.getTime(), new Date("2026-09-16T00:00:00").getTime())
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
test("normalizeEvent accepts daily, weekly, and monthly repeating values", () => {
|
|
139
|
-
for (const value of ["daily", "weekly", "monthly"]) {
|
|
140
|
-
assert.equal(
|
|
141
|
-
normalizeEvent({ repeating: value, start: "2026-09-01T09:00:00" }, 0)
|
|
142
|
-
.repeating,
|
|
143
|
-
value,
|
|
144
|
-
)
|
|
145
|
-
}
|
|
146
|
-
assert.equal(
|
|
147
|
-
normalizeEvent({ repeating: "yearly", start: "2026-09-01T09:00:00" }, 0)
|
|
148
|
-
.repeating,
|
|
149
|
-
undefined,
|
|
150
|
-
)
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
test("normalizeEvent reads all ten enhanced fields", () => {
|
|
154
|
-
const event = normalizeEvent(
|
|
155
|
-
{
|
|
156
|
-
allDay: true,
|
|
157
|
-
attendees: ["Alex", "Sam", "Alex", ""],
|
|
158
|
-
excludeDates: ["2026-09-17", "invalid"],
|
|
159
|
-
location: "Board room",
|
|
160
|
-
organizer: "Taylor",
|
|
161
|
-
reminders: [30, 0, 30, -1, 2.5],
|
|
162
|
-
repeatUntil: "2026-09-30",
|
|
163
|
-
start: "2026-09-16",
|
|
164
|
-
status: "tentative",
|
|
165
|
-
tags: ["finance", "quarterly", "finance"],
|
|
166
|
-
title: "Director review",
|
|
167
|
-
url: "https://example.com/review",
|
|
168
|
-
},
|
|
169
|
-
0,
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
assert.equal(event.allDay, true)
|
|
173
|
-
assert.equal(event.end.getTime() - event.start.getTime(), 86_400_000)
|
|
174
|
-
assert.deepEqual(event.attendees, ["Alex", "Sam"])
|
|
175
|
-
assert.deepEqual(
|
|
176
|
-
event.excludeDates?.map((date) => date.getDate()),
|
|
177
|
-
[17],
|
|
178
|
-
)
|
|
179
|
-
assert.equal(event.location, "Board room")
|
|
180
|
-
assert.equal(event.organizer, "Taylor")
|
|
181
|
-
assert.deepEqual(event.reminders, [0, 30])
|
|
182
|
-
assert.equal(event.repeatUntil?.getDate(), 30)
|
|
183
|
-
assert.equal(event.status, "tentative")
|
|
184
|
-
assert.deepEqual(event.tags, ["finance", "quarterly"])
|
|
185
|
-
assert.equal(event.url, "https://example.com/review")
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
test("repeatUntil and excludeDates limit repeating events", () => {
|
|
189
|
-
const event = normalizeEvent(
|
|
190
|
-
{
|
|
191
|
-
excludeDates: ["2026-09-03"],
|
|
192
|
-
repeating: "daily",
|
|
193
|
-
repeatUntil: "2026-09-04",
|
|
194
|
-
start: "2026-09-01T09:00:00",
|
|
195
|
-
title: "Check-in",
|
|
196
|
-
},
|
|
197
|
-
0,
|
|
198
|
-
)
|
|
199
|
-
const occurrences = eventsInRange(
|
|
200
|
-
[event],
|
|
201
|
-
new Date("2026-09-01T00:00:00"),
|
|
202
|
-
new Date("2026-09-06T00:00:00"),
|
|
203
|
-
)
|
|
204
|
-
|
|
205
|
-
assert.deepEqual(
|
|
206
|
-
occurrences.map((item) => item.start.getDate()),
|
|
207
|
-
[1, 2, 4],
|
|
208
|
-
)
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
test("eventsInRange expands daily, weekly, and monthly events", () => {
|
|
212
|
-
const events = parseEvents([
|
|
213
|
-
{
|
|
214
|
-
id: "daily",
|
|
215
|
-
repeating: "daily",
|
|
216
|
-
start: "2026-09-01T09:00:00",
|
|
217
|
-
end: "2026-09-01T09:30:00",
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
id: "weekly",
|
|
221
|
-
repeating: "weekly",
|
|
222
|
-
start: "2026-09-02T10:00:00",
|
|
223
|
-
end: "2026-09-02T10:30:00",
|
|
224
|
-
},
|
|
225
|
-
{
|
|
226
|
-
id: "monthly",
|
|
227
|
-
repeating: "monthly",
|
|
228
|
-
start: "2026-08-09T11:00:00",
|
|
229
|
-
end: "2026-08-09T11:30:00",
|
|
230
|
-
},
|
|
231
|
-
])
|
|
232
|
-
const occurrences = eventsInRange(
|
|
233
|
-
events,
|
|
234
|
-
new Date("2026-09-09T00:00:00"),
|
|
235
|
-
new Date("2026-09-10T00:00:00"),
|
|
236
|
-
)
|
|
237
|
-
assert.deepEqual(
|
|
238
|
-
occurrences.map((event) => event.id.split(":")[0]),
|
|
239
|
-
["daily", "weekly", "monthly"],
|
|
240
|
-
)
|
|
241
|
-
assert.ok(occurrences.every((event) => event.start instanceof Date))
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
test("validation accepts a valid object document", () => {
|
|
245
|
-
assert.deepEqual(
|
|
246
|
-
validateCalendarFile({
|
|
247
|
-
title: "Team",
|
|
248
|
-
events: [{ id: "1", title: "Standup", start: "2026-09-10T09:00:00" }],
|
|
249
|
-
}),
|
|
250
|
-
[],
|
|
251
|
-
)
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
test("validation accepts a top-level event array", () => {
|
|
255
|
-
assert.deepEqual(validateCalendarFile([{ title: "Standup" }]), [])
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
test("validation rejects non-object non-array documents", () => {
|
|
259
|
-
assert.deepEqual(validateCalendarFile("nope"), [
|
|
260
|
-
{
|
|
261
|
-
path: "#",
|
|
262
|
-
message: "calendar.json must contain an object or an array of events",
|
|
263
|
-
},
|
|
264
|
-
])
|
|
265
|
-
})
|
|
266
|
-
|
|
267
|
-
test("validation rejects a missing events array", () => {
|
|
268
|
-
assert.deepEqual(validateCalendarFile({ title: "Team" }), [
|
|
269
|
-
{ path: "events", message: "must be an array of events" },
|
|
270
|
-
])
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
test("validation flags events without any title", () => {
|
|
274
|
-
const issues = validateCalendarFile({
|
|
275
|
-
events: [{ id: "1", start: "2026-09-10T09:00:00" }],
|
|
276
|
-
})
|
|
277
|
-
assert.equal(issues[0]?.path, "events[0]")
|
|
278
|
-
assert.match(issues[0]?.message ?? "", /has no title/)
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
test("validation flags unparseable start and end dates", () => {
|
|
282
|
-
const issues = validateCalendarFile({
|
|
283
|
-
events: [{ title: "Broken", start: "not-a-date", end: "also-bad" }],
|
|
284
|
-
})
|
|
285
|
-
assert.deepEqual(
|
|
286
|
-
issues.map((issue) => issue.path),
|
|
287
|
-
["events[0].start", "events[0].end"],
|
|
288
|
-
)
|
|
289
|
-
assert.ok(
|
|
290
|
-
issues.every((issue) =>
|
|
291
|
-
/could not be parsed as a date/.test(issue.message),
|
|
292
|
-
),
|
|
293
|
-
)
|
|
294
|
-
})
|
|
295
|
-
|
|
296
|
-
test("validation flags bad enums and non-boolean allDay", () => {
|
|
297
|
-
const issues = validateCalendarFile({
|
|
298
|
-
events: [
|
|
299
|
-
{
|
|
300
|
-
title: "One",
|
|
301
|
-
start: "2026-09-10T09:00:00",
|
|
302
|
-
allDay: "yes",
|
|
303
|
-
repeating: "yearly",
|
|
304
|
-
status: "maybe",
|
|
305
|
-
},
|
|
306
|
-
],
|
|
307
|
-
})
|
|
308
|
-
assert.deepEqual(
|
|
309
|
-
issues.map((issue) => issue.path),
|
|
310
|
-
["events[0].allDay", "events[0].repeating", "events[0].status"],
|
|
311
|
-
)
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
test("parseCalendarSource reports invalid JSON as an issue", () => {
|
|
315
|
-
const parsed = parseCalendarSource('{"events": [')
|
|
316
|
-
assert.equal(parsed.ok, false)
|
|
317
|
-
if (parsed.ok) return
|
|
318
|
-
assert.equal(parsed.issues[0]?.path, "#")
|
|
319
|
-
assert.match(parsed.issues[0]?.message ?? "", /not valid JSON/)
|
|
320
|
-
})
|
|
321
|
-
|
|
322
|
-
test("calendarIssueSummary is compact for nav badges", () => {
|
|
323
|
-
assert.equal(
|
|
324
|
-
calendarIssueSummary([{ path: "events[2].start", message: "bad date" }]),
|
|
325
|
-
"events[2].start: bad date",
|
|
326
|
-
)
|
|
327
|
-
assert.equal(
|
|
328
|
-
calendarIssueSummary([
|
|
329
|
-
{ path: "events[0].start", message: "bad date" },
|
|
330
|
-
{ path: "events[1].start", message: "bad date" },
|
|
331
|
-
]),
|
|
332
|
-
"2 problems found in calendar.json (first: events[0].start — bad date)",
|
|
333
|
-
)
|
|
334
|
-
})
|
|
335
|
-
|
|
336
|
-
test("starter calendar source validates and parses", () => {
|
|
337
|
-
const source = starterCalendarSource()
|
|
338
|
-
const validation = parseCalendarSource(source)
|
|
339
|
-
assert.equal(validation.ok, true)
|
|
340
|
-
const document = parseCalendarJson(source)
|
|
341
|
-
assert.equal(eventCount(document), 1)
|
|
342
|
-
const parsed = parseCalendar(JSON.parse(source))
|
|
343
|
-
assert.equal(parsed?.events[0]?.title, "First event")
|
|
344
|
-
})
|
|
345
|
-
|
|
346
|
-
test("mergeCalendars combines nested files and assigns their groups", () => {
|
|
347
|
-
const merged = mergeCalendars([
|
|
348
|
-
{
|
|
349
|
-
calendar: parseCalendar({
|
|
350
|
-
color: "blue",
|
|
351
|
-
group: "Company",
|
|
352
|
-
title: "Example Workspace Calendar",
|
|
353
|
-
events: [{ title: "Check-in", start: "2026-09-16T08:30:00" }],
|
|
354
|
-
}),
|
|
355
|
-
folder: "/tmp/kennel-example/example-workspace",
|
|
356
|
-
group: "example-workspace",
|
|
357
|
-
path: "calendar.json",
|
|
358
|
-
},
|
|
359
|
-
{
|
|
360
|
-
calendar: parseCalendar({
|
|
361
|
-
color: "green",
|
|
362
|
-
group: "Accounting",
|
|
363
|
-
events: [{ title: "Payroll", start: "2026-09-16T10:00:00" }],
|
|
364
|
-
}),
|
|
365
|
-
folder: "/tmp/kennel-example/example-workspace/clients/client-one",
|
|
366
|
-
group: "accounting",
|
|
367
|
-
path: "accounting/calendar.json",
|
|
368
|
-
},
|
|
369
|
-
])
|
|
370
|
-
|
|
371
|
-
assert.equal(merged.title, "Example Workspace Calendar")
|
|
372
|
-
assert.deepEqual(
|
|
373
|
-
merged.events.map((event) => [
|
|
374
|
-
event.title,
|
|
375
|
-
event.group,
|
|
376
|
-
event.groupColor,
|
|
377
|
-
event.id,
|
|
378
|
-
]),
|
|
379
|
-
[
|
|
380
|
-
["Check-in", "Company", "blue", "calendar.json:0"],
|
|
381
|
-
["Payroll", "Accounting", "green", "accounting/calendar.json:0"],
|
|
382
|
-
],
|
|
383
|
-
)
|
|
384
|
-
// Every merged event carries its owning folder for the shared folder filter.
|
|
385
|
-
assert.deepEqual(
|
|
386
|
-
merged.events.map((event) => event.folder),
|
|
387
|
-
[
|
|
388
|
-
"/tmp/kennel-example/example-workspace",
|
|
389
|
-
"/tmp/kennel-example/example-workspace/clients/client-one",
|
|
390
|
-
],
|
|
391
|
-
)
|
|
392
|
-
})
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict"
|
|
2
|
-
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"
|
|
3
|
-
import { tmpdir } from "node:os"
|
|
4
|
-
import { join } from "node:path"
|
|
5
|
-
import test from "node:test"
|
|
6
|
-
|
|
7
|
-
import { createCalendarPlugin } from "../src/server.ts"
|
|
8
|
-
|
|
9
|
-
const plugin = createCalendarPlugin()
|
|
10
|
-
|
|
11
|
-
test("handle merges nested calendars and stamps each event's owning folder", async () => {
|
|
12
|
-
const folder = await mkdtemp(join(tmpdir(), "kennel-calendar-"))
|
|
13
|
-
try {
|
|
14
|
-
await writeFile(
|
|
15
|
-
join(folder, "calendar.json"),
|
|
16
|
-
JSON.stringify({
|
|
17
|
-
events: [{ title: "Root event", start: "2026-09-10T09:00:00" }],
|
|
18
|
-
}),
|
|
19
|
-
)
|
|
20
|
-
const nested = join(folder, "clients", "client-one")
|
|
21
|
-
await mkdir(nested, { recursive: true })
|
|
22
|
-
await writeFile(
|
|
23
|
-
join(nested, "calendar.json"),
|
|
24
|
-
JSON.stringify({
|
|
25
|
-
events: [{ title: "Nested event", start: "2026-09-11T09:00:00" }],
|
|
26
|
-
}),
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
const result = await plugin.handle({ folder }, "")
|
|
30
|
-
assert.equal(result.kind, "available")
|
|
31
|
-
if (result.kind !== "available") return
|
|
32
|
-
assert.equal(result.data.status, "available")
|
|
33
|
-
assert.equal(result.data.calendar.items.length, 2)
|
|
34
|
-
const folders = result.data.calendar.items
|
|
35
|
-
.map((event) => event.folder)
|
|
36
|
-
.sort()
|
|
37
|
-
assert.deepEqual(folders, [folder, nested].sort())
|
|
38
|
-
} finally {
|
|
39
|
-
await rm(folder, { recursive: true, force: true })
|
|
40
|
-
}
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
test("detect is unavailable without any calendar.json", async () => {
|
|
44
|
-
const folder = await mkdtemp(join(tmpdir(), "kennel-calendar-"))
|
|
45
|
-
try {
|
|
46
|
-
const detection = await plugin.detect({ folder })
|
|
47
|
-
assert.equal(detection.status, "unavailable")
|
|
48
|
-
} finally {
|
|
49
|
-
await rm(folder, { recursive: true, force: true })
|
|
50
|
-
}
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
test("detect is available with a valid calendar.json and reports its count", async () => {
|
|
54
|
-
const folder = await mkdtemp(join(tmpdir(), "kennel-calendar-"))
|
|
55
|
-
try {
|
|
56
|
-
await writeFile(
|
|
57
|
-
join(folder, "calendar.json"),
|
|
58
|
-
JSON.stringify({
|
|
59
|
-
events: [{ title: "Standup", start: "2026-09-10T09:00:00" }],
|
|
60
|
-
}),
|
|
61
|
-
)
|
|
62
|
-
const detection = await plugin.detect({ folder })
|
|
63
|
-
assert.equal(detection.status, "available")
|
|
64
|
-
assert.equal(detection.count, 1)
|
|
65
|
-
} finally {
|
|
66
|
-
await rm(folder, { recursive: true, force: true })
|
|
67
|
-
}
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
test("detect reports an error when a calendar.json exists but is invalid", async () => {
|
|
71
|
-
const folder = await mkdtemp(join(tmpdir(), "kennel-calendar-"))
|
|
72
|
-
try {
|
|
73
|
-
await writeFile(
|
|
74
|
-
join(folder, "calendar.json"),
|
|
75
|
-
JSON.stringify({ title: "No events here" }),
|
|
76
|
-
)
|
|
77
|
-
const detection = await plugin.detect({ folder })
|
|
78
|
-
assert.equal(detection.status, "error")
|
|
79
|
-
if (detection.status !== "error") return
|
|
80
|
-
assert.match(detection.error, /must be an array of events/)
|
|
81
|
-
} finally {
|
|
82
|
-
await rm(folder, { recursive: true, force: true })
|
|
83
|
-
}
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
test("handle returns in-band issues for a broken file while keeping valid events", async () => {
|
|
87
|
-
const folder = await mkdtemp(join(tmpdir(), "kennel-calendar-"))
|
|
88
|
-
try {
|
|
89
|
-
await writeFile(
|
|
90
|
-
join(folder, "calendar.json"),
|
|
91
|
-
JSON.stringify({
|
|
92
|
-
events: [
|
|
93
|
-
{ title: "Good", start: "2026-09-10T09:00:00" },
|
|
94
|
-
{ title: "Bad", start: "not-a-date" },
|
|
95
|
-
],
|
|
96
|
-
}),
|
|
97
|
-
)
|
|
98
|
-
const result = await plugin.handle({ folder }, "")
|
|
99
|
-
assert.equal(result.kind, "available")
|
|
100
|
-
if (result.kind !== "available") return
|
|
101
|
-
assert.equal(result.data.status, "error")
|
|
102
|
-
assert.equal(result.data.issues.length, 1)
|
|
103
|
-
assert.equal(result.data.issues[0].path, "calendar.json: events[1].start")
|
|
104
|
-
// The whole file is flagged and excluded; no events render.
|
|
105
|
-
assert.equal(result.data.calendar.items.length, 0)
|
|
106
|
-
} finally {
|
|
107
|
-
await rm(folder, { recursive: true, force: true })
|
|
108
|
-
}
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
test("init scaffolds a valid calendar.json starter", async () => {
|
|
112
|
-
const folder = await mkdtemp(join(tmpdir(), "kennel-calendar-"))
|
|
113
|
-
try {
|
|
114
|
-
const result = await plugin.init({ folder })
|
|
115
|
-
assert.equal(result.files.length, 1)
|
|
116
|
-
assert.equal(result.files[0].path, "calendar.json")
|
|
117
|
-
// The CLI writes the returned content; simulate that write and confirm
|
|
118
|
-
// the starter immediately passes detection.
|
|
119
|
-
await writeFile(join(folder, "calendar.json"), result.files[0].content)
|
|
120
|
-
const detection = await plugin.detect({ folder })
|
|
121
|
-
assert.equal(detection.status, "available")
|
|
122
|
-
assert.equal(detection.count, 1)
|
|
123
|
-
} finally {
|
|
124
|
-
await rm(folder, { recursive: true, force: true })
|
|
125
|
-
}
|
|
126
|
-
})
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
-
"extends": "@raggle-ai/tsconfig/base.json",
|
|
4
|
-
"compilerOptions": {
|
|
5
|
-
"target": "ES2022",
|
|
6
|
-
"lib": ["dom", "dom.iterable", "esnext"],
|
|
7
|
-
"jsx": "preserve",
|
|
8
|
-
"baseUrl": ".",
|
|
9
|
-
"allowImportingTsExtensions": true,
|
|
10
|
-
// local copy: bun doesn't read paths inherited via `extends`
|
|
11
|
-
"paths": {
|
|
12
|
-
"~/*": ["${configDir}/../../packages/*/src"]
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
"include": ["src"],
|
|
16
|
-
"exclude": ["node_modules", "dist", "eslint.config.mjs"]
|
|
17
|
-
}
|