@sonordev/site-kit 6.3.6 → 6.4.0
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/agent-manifest.json +8 -2
- package/dist/{AnalyticsProvider-XDVJTAPN.mjs → AnalyticsProvider-4K5T7OQ7.mjs} +6 -6
- package/dist/{AnalyticsProvider-XDVJTAPN.mjs.map → AnalyticsProvider-4K5T7OQ7.mjs.map} +1 -1
- package/dist/{AnalyticsProvider-L3GWK5PO.js → AnalyticsProvider-QXZN5O6D.js} +10 -10
- package/dist/{AnalyticsProvider-L3GWK5PO.js.map → AnalyticsProvider-QXZN5O6D.js.map} +1 -1
- package/dist/{ArticleViewTracker-7AJJTGFY.mjs → ArticleViewTracker-BUG2TOHS.mjs} +5 -5
- package/dist/{ArticleViewTracker-7AJJTGFY.mjs.map → ArticleViewTracker-BUG2TOHS.mjs.map} +1 -1
- package/dist/{ArticleViewTracker-YMGW6ZXB.js → ArticleViewTracker-FFMHR7AY.js} +6 -6
- package/dist/{ArticleViewTracker-YMGW6ZXB.js.map → ArticleViewTracker-FFMHR7AY.js.map} +1 -1
- package/dist/{ChatWidget-MR64ZLOG.mjs → ChatWidget-CRQPBDEF.mjs} +7 -7
- package/dist/{ChatWidget-MR64ZLOG.mjs.map → ChatWidget-CRQPBDEF.mjs.map} +1 -1
- package/dist/{ChatWidget-IAYEXLNX.js → ChatWidget-XFYLVXAX.js} +10 -10
- package/dist/{ChatWidget-IAYEXLNX.js.map → ChatWidget-XFYLVXAX.js.map} +1 -1
- package/dist/EngageWidget-S234YAT2.js +21 -0
- package/dist/{EngageWidget-BF7DH4GO.js.map → EngageWidget-S234YAT2.js.map} +1 -1
- package/dist/EngageWidget-SQU4T7CI.mjs +12 -0
- package/dist/{EngageWidget-RANSIKC3.mjs.map → EngageWidget-SQU4T7CI.mjs.map} +1 -1
- package/dist/{FileField-7ZNQ5BID.mjs → FileField-HNEBEBLB.mjs} +5 -5
- package/dist/{FileField-7ZNQ5BID.mjs.map → FileField-HNEBEBLB.mjs.map} +1 -1
- package/dist/{FileField-VRKATE4G.js → FileField-XG4EYAZW.js} +6 -6
- package/dist/{FileField-VRKATE4G.js.map → FileField-XG4EYAZW.js.map} +1 -1
- package/dist/{FormSpotlight-VQJ2QWIU.js → FormSpotlight-IODREOPN.js} +9 -9
- package/dist/{FormSpotlight-VQJ2QWIU.js.map → FormSpotlight-IODREOPN.js.map} +1 -1
- package/dist/{FormSpotlight-QNH7XGYS.mjs → FormSpotlight-VKD774PZ.mjs} +3 -3
- package/dist/{FormSpotlight-QNH7XGYS.mjs.map → FormSpotlight-VKD774PZ.mjs.map} +1 -1
- package/dist/{FormStage-3JIN6X6O.js → FormStage-37KKZQH4.js} +9 -9
- package/dist/{FormStage-3JIN6X6O.js.map → FormStage-37KKZQH4.js.map} +1 -1
- package/dist/{FormStage-XB4JNFCH.mjs → FormStage-PRZNRYNC.mjs} +3 -3
- package/dist/{FormStage-XB4JNFCH.mjs.map → FormStage-PRZNRYNC.mjs.map} +1 -1
- package/dist/ManagedForm-4PZOCKCV.mjs +16 -0
- package/dist/{ManagedForm-E6F4FLVY.mjs.map → ManagedForm-4PZOCKCV.mjs.map} +1 -1
- package/dist/ManagedForm-JCMEE626.js +25 -0
- package/dist/{ManagedForm-Q5UFMUBE.js.map → ManagedForm-JCMEE626.js.map} +1 -1
- package/dist/{ManagedNewsletterForm-UTNOJ5T4.js → ManagedNewsletterForm-CTC33B2U.js} +7 -7
- package/dist/{ManagedNewsletterForm-UTNOJ5T4.js.map → ManagedNewsletterForm-CTC33B2U.js.map} +1 -1
- package/dist/{ManagedNewsletterForm-OEEC3GVY.mjs → ManagedNewsletterForm-KMZG24OE.mjs} +6 -6
- package/dist/{ManagedNewsletterForm-OEEC3GVY.mjs.map → ManagedNewsletterForm-KMZG24OE.mjs.map} +1 -1
- package/dist/{SignalCore-4AUWHMYK.js → SignalCore-YHT7WPXV.js} +11 -11
- package/dist/{SignalCore-4AUWHMYK.js.map → SignalCore-YHT7WPXV.js.map} +1 -1
- package/dist/{SignalCore-PWN5X6FF.mjs → SignalCore-YIPKKIUG.mjs} +5 -5
- package/dist/{SignalCore-PWN5X6FF.mjs.map → SignalCore-YIPKKIUG.mjs.map} +1 -1
- package/dist/SitemapSync-GN4OXIM2.js +18 -0
- package/dist/{SitemapSync-DHJ25PSH.js.map → SitemapSync-GN4OXIM2.js.map} +1 -1
- package/dist/SitemapSync-NG763CGN.mjs +9 -0
- package/dist/{SitemapSync-ZA2MNM63.mjs.map → SitemapSync-NG763CGN.mjs.map} +1 -1
- package/dist/analytics/index.js +9 -9
- package/dist/analytics/index.mjs +4 -4
- package/dist/articles/AuthorPage.d.ts +1 -1
- package/dist/articles/AuthorPage.d.ts.map +1 -1
- package/dist/articles/author-schema.d.ts +59 -13
- package/dist/articles/author-schema.d.ts.map +1 -1
- package/dist/articles/index.js +24 -23
- package/dist/articles/index.js.map +1 -1
- package/dist/articles/index.mjs +12 -11
- package/dist/articles/index.mjs.map +1 -1
- package/dist/articles/routes.d.ts +9 -0
- package/dist/articles/routes.d.ts.map +1 -1
- package/dist/articles/server-core.d.ts +5 -2
- package/dist/articles/server-core.d.ts.map +1 -1
- package/dist/articles/server-ui.js +45 -45
- package/dist/articles/server-ui.mjs +8 -8
- package/dist/articles/server.d.ts +5 -2
- package/dist/articles/server.d.ts.map +1 -1
- package/dist/articles/server.js +72 -56
- package/dist/articles/server.mjs +3 -3
- package/dist/articles/types.d.ts +17 -0
- package/dist/articles/types.d.ts.map +1 -1
- package/dist/{chunk-PQICWWQW.mjs → chunk-24LFAQEG.mjs} +5 -5
- package/dist/{chunk-PQICWWQW.mjs.map → chunk-24LFAQEG.mjs.map} +1 -1
- package/dist/chunk-3AOKYAZ2.js +135 -0
- package/dist/chunk-3AOKYAZ2.js.map +1 -0
- package/dist/{chunk-UJIKP35G.mjs → chunk-4C3INRHE.mjs} +4 -4
- package/dist/{chunk-UJIKP35G.mjs.map → chunk-4C3INRHE.mjs.map} +1 -1
- package/dist/{chunk-ZR43XFUM.js → chunk-55J2KE7D.js} +3 -3
- package/dist/chunk-55J2KE7D.js.map +1 -0
- package/dist/{chunk-B2NO5QVY.mjs → chunk-5BARLEAI.mjs} +3 -3
- package/dist/{chunk-B2NO5QVY.mjs.map → chunk-5BARLEAI.mjs.map} +1 -1
- package/dist/{chunk-BJKA3XRS.mjs → chunk-5T3J24K4.mjs} +4 -4
- package/dist/{chunk-BJKA3XRS.mjs.map → chunk-5T3J24K4.mjs.map} +1 -1
- package/dist/{chunk-3J5L5DIK.js → chunk-5W4S7XQD.js} +3 -3
- package/dist/{chunk-3J5L5DIK.js.map → chunk-5W4S7XQD.js.map} +1 -1
- package/dist/{chunk-6DCAMW62.mjs → chunk-6C47333H.mjs} +3 -3
- package/dist/chunk-6C47333H.mjs.map +1 -0
- package/dist/{chunk-YKZ3EVAQ.mjs → chunk-6QPKC6XP.mjs} +3 -3
- package/dist/{chunk-YKZ3EVAQ.mjs.map → chunk-6QPKC6XP.mjs.map} +1 -1
- package/dist/{chunk-JOYMIGR2.mjs → chunk-7K6L33BX.mjs} +22 -22
- package/dist/{chunk-JS75ORH7.js → chunk-ATPQ6264.js} +24 -24
- package/dist/{chunk-5J4FKRGH.mjs → chunk-BI7DH42Z.mjs} +4 -4
- package/dist/{chunk-5J4FKRGH.mjs.map → chunk-BI7DH42Z.mjs.map} +1 -1
- package/dist/{chunk-J4C3JAJF.js → chunk-C7J4XLBN.js} +15 -15
- package/dist/{chunk-J4C3JAJF.js.map → chunk-C7J4XLBN.js.map} +1 -1
- package/dist/{chunk-LWCAZDY2.js → chunk-CDRQYJUT.js} +4 -4
- package/dist/{chunk-LWCAZDY2.js.map → chunk-CDRQYJUT.js.map} +1 -1
- package/dist/chunk-CIB3RHNZ.js +8 -0
- package/dist/{chunk-6SBRD5KT.js.map → chunk-CIB3RHNZ.js.map} +1 -1
- package/dist/{chunk-FRUBUQOH.js → chunk-CUZ3I6CO.js} +5 -5
- package/dist/{chunk-FRUBUQOH.js.map → chunk-CUZ3I6CO.js.map} +1 -1
- package/dist/{chunk-PMWF5CRL.js → chunk-EWDWLY74.js} +11 -11
- package/dist/{chunk-PMWF5CRL.js.map → chunk-EWDWLY74.js.map} +1 -1
- package/dist/{chunk-OLVCGOXZ.js → chunk-F2GECCK7.js} +12 -12
- package/dist/{chunk-OLVCGOXZ.js.map → chunk-F2GECCK7.js.map} +1 -1
- package/dist/chunk-GFSQMK35.mjs +127 -0
- package/dist/chunk-GFSQMK35.mjs.map +1 -0
- package/dist/{chunk-2MEMK5XC.js → chunk-GGGX54ZE.js} +14 -14
- package/dist/{chunk-2MEMK5XC.js.map → chunk-GGGX54ZE.js.map} +1 -1
- package/dist/{chunk-6MOGGEY3.mjs → chunk-GSU6VTCZ.mjs} +3 -3
- package/dist/{chunk-6MOGGEY3.mjs.map → chunk-GSU6VTCZ.mjs.map} +1 -1
- package/dist/{chunk-3POB4JKR.js → chunk-GXSSFQ5D.js} +15 -15
- package/dist/{chunk-3POB4JKR.js.map → chunk-GXSSFQ5D.js.map} +1 -1
- package/dist/{chunk-BJ4T65OK.mjs → chunk-H4TSADY3.mjs} +3 -3
- package/dist/{chunk-BJ4T65OK.mjs.map → chunk-H4TSADY3.mjs.map} +1 -1
- package/dist/{chunk-2UZULHI4.js → chunk-HSLPN3QK.js} +7 -7
- package/dist/{chunk-2UZULHI4.js.map → chunk-HSLPN3QK.js.map} +1 -1
- package/dist/{chunk-EB33U2WT.js → chunk-HTCNRTN6.js} +129 -69
- package/dist/chunk-HTCNRTN6.js.map +1 -0
- package/dist/{chunk-3Q3Y6OQE.mjs → chunk-JXRGIE3V.mjs} +4 -4
- package/dist/{chunk-3Q3Y6OQE.mjs.map → chunk-JXRGIE3V.mjs.map} +1 -1
- package/dist/chunk-K3PVUXKB.mjs +6 -0
- package/dist/{chunk-3V6DIQI7.mjs.map → chunk-K3PVUXKB.mjs.map} +1 -1
- package/dist/{chunk-KMONA7CT.mjs → chunk-KOPFFKG2.mjs} +3 -3
- package/dist/{chunk-KMONA7CT.mjs.map → chunk-KOPFFKG2.mjs.map} +1 -1
- package/dist/{chunk-UAOQVHZW.js → chunk-KZBDH5A3.js} +4 -4
- package/dist/{chunk-UAOQVHZW.js.map → chunk-KZBDH5A3.js.map} +1 -1
- package/dist/{chunk-SB4CJX2X.mjs → chunk-LPDXS2B2.mjs} +3 -3
- package/dist/{chunk-SB4CJX2X.mjs.map → chunk-LPDXS2B2.mjs.map} +1 -1
- package/dist/{chunk-6I5O5CTL.mjs → chunk-M3JCHPK3.mjs} +127 -67
- package/dist/chunk-M3JCHPK3.mjs.map +1 -0
- package/dist/{chunk-LFF32POH.mjs → chunk-M62L2N5A.mjs} +3 -3
- package/dist/{chunk-LFF32POH.mjs.map → chunk-M62L2N5A.mjs.map} +1 -1
- package/dist/{chunk-G3L26XQ7.js → chunk-OIPG3EGM.js} +5 -5
- package/dist/{chunk-G3L26XQ7.js.map → chunk-OIPG3EGM.js.map} +1 -1
- package/dist/{chunk-X3MY4PL7.mjs → chunk-QAQTDHHT.mjs} +10 -14
- package/dist/chunk-QAQTDHHT.mjs.map +1 -0
- package/dist/{chunk-KB7SIIVY.mjs → chunk-QOKMH2TD.mjs} +3 -3
- package/dist/{chunk-KB7SIIVY.mjs.map → chunk-QOKMH2TD.mjs.map} +1 -1
- package/dist/{chunk-HXHRS3PX.mjs → chunk-RCB6HEHM.mjs} +3 -3
- package/dist/{chunk-HXHRS3PX.mjs.map → chunk-RCB6HEHM.mjs.map} +1 -1
- package/dist/{chunk-NNZDWY3L.mjs → chunk-RH4XEXTK.mjs} +3 -3
- package/dist/{chunk-NAA5J2JM.js → chunk-RQROZLH3.js} +47 -51
- package/dist/chunk-RQROZLH3.js.map +1 -0
- package/dist/{chunk-VGBUC2JP.js → chunk-S5QKNXLQ.js} +10 -10
- package/dist/{chunk-VGBUC2JP.js.map → chunk-S5QKNXLQ.js.map} +1 -1
- package/dist/{chunk-5DZVQW35.mjs → chunk-SBTA6B6Y.mjs} +4 -4
- package/dist/{chunk-5DZVQW35.mjs.map → chunk-SBTA6B6Y.mjs.map} +1 -1
- package/dist/{chunk-5YA6SI4Z.mjs → chunk-TIFVAASU.mjs} +2 -2
- package/dist/{chunk-R26FAQWF.mjs → chunk-TPDDSEL3.mjs} +3 -3
- package/dist/{chunk-R26FAQWF.mjs.map → chunk-TPDDSEL3.mjs.map} +1 -1
- package/dist/{chunk-X5J3PYZJ.js → chunk-U4CYO4NG.js} +5 -5
- package/dist/{chunk-X5J3PYZJ.js.map → chunk-U4CYO4NG.js.map} +1 -1
- package/dist/{chunk-TE3YNZVH.js → chunk-U5NFIHLY.js} +4 -4
- package/dist/{chunk-TE3YNZVH.js.map → chunk-U5NFIHLY.js.map} +1 -1
- package/dist/{chunk-L6BQIDSZ.mjs → chunk-USQPRLSG.mjs} +8 -8
- package/dist/{chunk-L6BQIDSZ.mjs.map → chunk-USQPRLSG.mjs.map} +1 -1
- package/dist/{chunk-5XDOBM5S.js → chunk-VDEQVXNV.js} +3 -3
- package/dist/{chunk-5XDOBM5S.js.map → chunk-VDEQVXNV.js.map} +1 -1
- package/dist/{chunk-SAOUAIRS.js → chunk-VMNPLCZB.js} +14 -14
- package/dist/{chunk-SAOUAIRS.js.map → chunk-VMNPLCZB.js.map} +1 -1
- package/dist/{chunk-ANYCUTS5.js → chunk-W7IAG5YD.js} +3 -3
- package/dist/{chunk-ANYCUTS5.js.map → chunk-W7IAG5YD.js.map} +1 -1
- package/dist/{chunk-K6ZQVDP4.mjs → chunk-WIWHM47B.mjs} +3 -3
- package/dist/{chunk-K6ZQVDP4.mjs.map → chunk-WIWHM47B.mjs.map} +1 -1
- package/dist/{chunk-I7AZC5ZQ.js → chunk-XXSSUCJB.js} +2 -2
- package/dist/{chunk-7NFSSPGM.js → chunk-XZHR4E2D.js} +8 -8
- package/dist/{chunk-7NFSSPGM.js.map → chunk-XZHR4E2D.js.map} +1 -1
- package/dist/{chunk-E7EV2AHT.js → chunk-YACU35MY.js} +9 -9
- package/dist/{chunk-E7EV2AHT.js.map → chunk-YACU35MY.js.map} +1 -1
- package/dist/chunk-YMZNEDFP.js +39 -0
- package/dist/chunk-YMZNEDFP.js.map +1 -0
- package/dist/{chunk-CXYGKK4U.mjs → chunk-YRKWJCML.mjs} +3 -3
- package/dist/{chunk-CXYGKK4U.mjs.map → chunk-YRKWJCML.mjs.map} +1 -1
- package/dist/chunk-Z2URIOKG.mjs +36 -0
- package/dist/chunk-Z2URIOKG.mjs.map +1 -0
- package/dist/{chunk-W46QIVBM.mjs → chunk-ZEBOOUBP.mjs} +3 -3
- package/dist/{chunk-W46QIVBM.mjs.map → chunk-ZEBOOUBP.mjs.map} +1 -1
- package/dist/{chunk-SNXJXPB6.js → chunk-ZOBUA65N.js} +6 -6
- package/dist/{chunk-SNXJXPB6.js.map → chunk-ZOBUA65N.js.map} +1 -1
- package/dist/{chunk-NN2YE5WT.js → chunk-ZUQSVCMD.js} +3 -3
- package/dist/cli/index.js +211 -211
- package/dist/cli/index.mjs +151 -151
- package/dist/client/index.js +4 -4
- package/dist/client/index.mjs +3 -3
- package/dist/commerce/index.js +57 -57
- package/dist/commerce/index.mjs +5 -5
- package/dist/engage/DesignRenderer.d.ts.map +1 -1
- package/dist/engage/EngageWidget.d.ts.map +1 -1
- package/dist/engage/element-rules.d.ts +39 -0
- package/dist/engage/element-rules.d.ts.map +1 -0
- package/dist/engage/index.js +9 -9
- package/dist/engage/index.mjs +6 -6
- package/dist/fleet/index.js +8 -8
- package/dist/fleet/index.mjs +4 -4
- package/dist/forms/index.js +13 -13
- package/dist/forms/index.mjs +7 -7
- package/dist/forms/server.js +2 -2
- package/dist/forms/server.mjs +1 -1
- package/dist/images/index.js +11 -11
- package/dist/images/index.mjs +4 -4
- package/dist/index.js +63 -63
- package/dist/index.mjs +9 -9
- package/dist/layout/client.js +8 -8
- package/dist/layout/client.mjs +7 -7
- package/dist/layout/index.js +10 -10
- package/dist/layout/index.mjs +8 -8
- package/dist/{lib-6HOOMENJ.js → lib-6GWWPDJR.js} +7 -7
- package/dist/lib-BD4KKQBI.mjs +4 -0
- package/dist/{lib-R7IQ4ZHE.mjs → lib-N5PD6JNJ.mjs} +2 -2
- package/dist/{lib-BGAWCSUH.js → lib-WFB5RLEQ.js} +8 -8
- package/dist/llms/index.d.ts +2 -0
- package/dist/llms/index.d.ts.map +1 -1
- package/dist/llms/index.js +129 -14
- package/dist/llms/index.js.map +1 -1
- package/dist/llms/index.mjs +115 -5
- package/dist/llms/index.mjs.map +1 -1
- package/dist/llms/revalidate.d.ts +3 -0
- package/dist/llms/revalidate.d.ts.map +1 -1
- package/dist/llms/seo-revalidate.d.ts +105 -0
- package/dist/llms/seo-revalidate.d.ts.map +1 -0
- package/dist/maps/index.js +5 -5
- package/dist/maps/index.mjs +3 -3
- package/dist/mcp/transport.d.ts +166 -0
- package/dist/mcp/transport.d.ts.map +1 -0
- package/dist/mcp/transport.js +137 -0
- package/dist/mcp/transport.js.map +1 -0
- package/dist/mcp/transport.mjs +123 -0
- package/dist/mcp/transport.mjs.map +1 -0
- package/dist/search/index.js +4 -4
- package/dist/search/index.mjs +3 -3
- package/dist/seo/client.js +5 -5
- package/dist/seo/client.mjs +4 -4
- package/dist/seo/index.js +5 -5
- package/dist/seo/index.mjs +4 -4
- package/dist/seo/register-sitemap-cli.js +3 -3
- package/dist/seo/register-sitemap-cli.mjs +2 -2
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +2 -2
- package/dist/shared/build-entries.d.ts +7 -0
- package/dist/shared/build-entries.d.ts.map +1 -1
- package/dist/shared/request-guards.d.ts +27 -0
- package/dist/shared/request-guards.d.ts.map +1 -0
- package/dist/shared/version.d.ts +1 -1
- package/dist/signal/index.js +11 -11
- package/dist/signal/index.mjs +2 -2
- package/dist/sitemap/index.js +3 -3
- package/dist/sitemap/index.mjs +2 -2
- package/dist/sync/index.js +17 -17
- package/dist/sync/index.mjs +4 -4
- package/dist/{web-vitals.attribution-7LKLZ5GR.mjs → web-vitals.attribution-CHXXWO72.mjs} +3 -3
- package/dist/web-vitals.attribution-CHXXWO72.mjs.map +1 -0
- package/dist/{web-vitals.attribution-ONVYQNRB.js → web-vitals.attribution-SQVZNHN3.js} +3 -3
- package/dist/web-vitals.attribution-SQVZNHN3.js.map +1 -0
- package/dist/{writeLLMsTxt-LYYC3ZSN.mjs → writeLLMsTxt-IYJILIU3.mjs} +3 -3
- package/dist/{writeLLMsTxt-XHLHJ5XY.js → writeLLMsTxt-SQG4B63X.js} +4 -4
- package/package.json +6 -1
- package/dist/EngageWidget-BF7DH4GO.js +0 -21
- package/dist/EngageWidget-RANSIKC3.mjs +0 -12
- package/dist/ManagedForm-E6F4FLVY.mjs +0 -16
- package/dist/ManagedForm-Q5UFMUBE.js +0 -25
- package/dist/SitemapSync-DHJ25PSH.js +0 -18
- package/dist/SitemapSync-ZA2MNM63.mjs +0 -9
- package/dist/chunk-3V6DIQI7.mjs +0 -6
- package/dist/chunk-6DCAMW62.mjs.map +0 -1
- package/dist/chunk-6I5O5CTL.mjs.map +0 -1
- package/dist/chunk-6SBRD5KT.js +0 -8
- package/dist/chunk-EB33U2WT.js.map +0 -1
- package/dist/chunk-NAA5J2JM.js.map +0 -1
- package/dist/chunk-X3MY4PL7.mjs.map +0 -1
- package/dist/chunk-YRCR7WY3.js +0 -65
- package/dist/chunk-YRCR7WY3.js.map +0 -1
- package/dist/chunk-ZL35JCDA.mjs +0 -61
- package/dist/chunk-ZL35JCDA.mjs.map +0 -1
- package/dist/chunk-ZR43XFUM.js.map +0 -1
- package/dist/lib-RSDMEKDQ.mjs +0 -4
- package/dist/web-vitals.attribution-7LKLZ5GR.mjs.map +0 -1
- package/dist/web-vitals.attribution-ONVYQNRB.js.map +0 -1
package/dist/llms/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/llms/handlers.ts","../../src/llms/sourcedFacts.ts","../../src/llms/revalidate.ts"],"names":["markdown"],"mappings":";;;;;;;;;;;;;;;;;;;AAYA,SAAS,SAAS,IAAA,EAAsB;AACtC,EAAA,MAAM,CAAA,GAAI,WAAW,MAAM,CAAA,CAAE,OAAO,IAAA,EAAM,MAAM,CAAA,CAAE,MAAA,CAAO,WAAW,CAAA;AACpE,EAAA,OAAO,MAAM,CAAC,CAAA,CAAA,CAAA;AAChB;AAGO,SAAS,mBAAA,CAAoB,MAAc,KAAA,EAA6C;AAC7F,EAAA,OAAO;AAAA,IACL,cAAA,EAAgB,2BAAA;AAAA,IAChB,eAAA,EACE,wFAAA;AAAA,IACF,IAAA,EAAM,SAAS,IAAI,CAAA;AAAA,IACnB,IAAA,EAAM,iBAAA;AAAA,IACN,GAAG;AAAA,GACL;AACF;AAsBO,IAAM,oBAAA,GAAyD,EAAE,cAAA,EAAgB,SAAA,EAAU;AAmC3F,SAAS,oBAAA,CACd,OAAA,GAAiC,EAAC,EACQ;AAC1C,EAAA,MAAM,EAAE,YAAA,GAAe,IAAA,EAAM,UAAU,IAAA,EAAM,GAAG,iBAAgB,GAAI,OAAA;AACpE,EAAA,MAAM,MAAA,GAAS,OAAA,GAAU,oBAAA,GAAuB,EAAC;AAEjD,EAAA,OAAO,eAAe,IAAI,QAAA,EAAuC;AAC/D,IAAA,IAAI;AAEF,MAAA,IAAI,YAAA,EAAc;AAChB,QAAA,MAAM,aAAa,IAAA,CAAK,OAAA,CAAQ,GAAA,EAAI,EAAG,UAAU,UAAU,CAAA;AAC3D,QAAA,IAAI,UAAA,CAAW,UAAU,CAAA,EAAG;AAC1B,UAAA,MAAMA,SAAAA,GAAW,YAAA,CAAa,UAAA,EAAY,OAAO,CAAA;AACjD,UAAA,OAAO,IAAI,SAASA,SAAAA,EAAU;AAAA,YAC5B,MAAA,EAAQ,GAAA;AAAA,YACR,OAAA,EAAS,oBAAoBA,SAAAA,EAAU,EAAE,GAAG,MAAA,EAAQ,YAAA,EAAc,UAAU;AAAA,WAC7E,CAAA;AAAA,QACH;AAAA,MACF;AAEA,MAAA,MAAM,EAAE,QAAA,EAAU,QAAA,EAAS,GAAI,MAAM,gBAAgB,eAAe,CAAA;AACpE,MAAA,OAAO,IAAI,SAAS,QAAA,EAAU;AAAA,QAC5B,MAAA,EAAQ,GAAA;AAAA,QACR,OAAA,EAAS,oBAAoB,QAAA,EAAU;AAAA,UACrC,GAAG,MAAA;AAAA,UACH,kBAAkB,QAAA,CAAS,YAAA;AAAA,UAC3B,YAAA,EAAc,QAAA,CAAS,QAAA,CAAS,IAAA,CAAK,GAAG;AAAA,SACzC;AAAA,OACF,CAAA;AAAA,IACH,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,8CAA8C,KAAK,CAAA;AACjE,MAAA,OAAO,IAAI,SAAS,iDAAA,EAAmD;AAAA,QACrE,MAAA,EAAQ,GAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACP,cAAA,EAAgB,2BAAA;AAAA,UAChB,eAAA,EAAiB;AAAA;AACnB,OACD,CAAA;AAAA,IACH;AAAA,EACF,CAAA;AACF;AAoBO,SAAS,wBAAA,CACd,OAAA,GAAiC,EAAC,EACQ;AAC1C,EAAA,MAAM,EAAE,YAAA,GAAe,IAAA,EAAM,UAAU,IAAA,EAAM,GAAG,iBAAgB,GAAI,OAAA;AACpE,EAAA,MAAM,MAAA,GAAS,OAAA,GAAU,oBAAA,GAAuB,EAAC;AAEjD,EAAA,OAAO,eAAe,IAAI,QAAA,EAAuC;AAC/D,IAAA,IAAI;AAEF,MAAA,IAAI,YAAA,EAAc;AAChB,QAAA,MAAM,aAAa,IAAA,CAAK,OAAA,CAAQ,GAAA,EAAI,EAAG,UAAU,eAAe,CAAA;AAChE,QAAA,IAAI,UAAA,CAAW,UAAU,CAAA,EAAG;AAC1B,UAAA,MAAMA,SAAAA,GAAW,YAAA,CAAa,UAAA,EAAY,OAAO,CAAA;AACjD,UAAA,OAAO,IAAI,SAASA,SAAAA,EAAU;AAAA,YAC5B,MAAA,EAAQ,GAAA;AAAA,YACR,OAAA,EAAS,oBAAoBA,SAAAA,EAAU,EAAE,GAAG,MAAA,EAAQ,YAAA,EAAc,UAAU;AAAA,WAC7E,CAAA;AAAA,QACH;AAAA,MACF;AAEA,MAAA,MAAM,EAAE,QAAA,EAAU,QAAA,EAAS,GAAI,MAAM,oBAAoB,eAAe,CAAA;AACxE,MAAA,OAAO,IAAI,SAAS,QAAA,EAAU;AAAA,QAC5B,MAAA,EAAQ,GAAA;AAAA,QACR,OAAA,EAAS,oBAAoB,QAAA,EAAU;AAAA,UACrC,GAAG,MAAA;AAAA,UACH,kBAAkB,QAAA,CAAS,YAAA;AAAA,UAC3B,YAAA,EAAc,QAAA,CAAS,QAAA,CAAS,IAAA,CAAK,GAAG;AAAA,SACzC;AAAA,OACF,CAAA;AAAA,IACH,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,mDAAmD,KAAK,CAAA;AACtE,MAAA,OAAO,IAAI,SAAS,sDAAA,EAAwD;AAAA,QAC1E,MAAA,EAAQ,GAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACP,cAAA,EAAgB,2BAAA;AAAA,UAChB,eAAA,EAAiB;AAAA;AACnB,OACD,CAAA;AAAA,IACH;AAAA,EACF,CAAA;AACF;;;AC7IA,IAAM,aAAA,GACJ,8KAAA;AAKF,SAAS,SAAS,CAAA,EAAwB;AACxC,EAAA,MAAM,IAAA,GAAA,CAAQ,EAAE,IAAA,IAAQ,EAAA,EAAI,QAAQ,MAAA,EAAQ,GAAG,EAAE,IAAA,EAAK;AACtD,EAAA,MAAM,MAAA,GAAA,CAAU,EAAE,UAAA,IAAc,EAAA,EAAI,QAAQ,MAAA,EAAQ,GAAG,EAAE,IAAA,EAAK;AAC9D,EAAA,IAAI,CAAC,IAAA,IAAQ,CAAC,MAAA,EAAQ,OAAO,EAAA;AAC7B,EAAA,IAAI,WAAA,GAAc,WAAW,MAAM,CAAA,CAAA;AACnC,EAAA,IAAI,CAAA,CAAE,IAAA,EAAM,WAAA,IAAe,CAAA,EAAA,EAAK,EAAE,IAAI,CAAA,CAAA,CAAA;AACtC,EAAA,IAAI,CAAA,CAAE,GAAA,EAAK,WAAA,IAAe,CAAA,EAAA,EAAK,EAAE,GAAG,CAAA,CAAA;AACpC,EAAA,OAAO,CAAA,EAAA,EAAK,IAAI,CAAA,QAAA,EAAM,WAAW,CAAA,CAAA;AACnC;AAMO,SAAS,yBAAA,CACd,KAAA,EACA,OAAA,GAAqC,EAAC,EACZ;AAC1B,EAAA,MAAM,EAAE,KAAA,GAAQ,eAAA,EAAiB,KAAA,GAAQ,aAAA,EAAe,UAAS,GAAI,OAAA;AACrE,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,GAAG,OAAO,IAAA;AAElC,EAAA,IAAI,QAAQ,KAAA,CAAM,GAAA,CAAI,QAAQ,CAAA,CAAE,OAAO,OAAO,CAAA;AAC9C,EAAA,IAAI,OAAO,QAAA,KAAa,QAAA,IAAY,QAAA,IAAY,CAAA,EAAG;AACjD,IAAA,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,QAAQ,CAAA;AAAA,EACjC;AACA,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AAE/B,EAAA,MAAM,OAAA,GAAU,KAAA,GAAQ,CAAA,EAAG,KAAK;;AAAA,EAAO,MAAM,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA,GAAK,KAAA,CAAM,KAAK,IAAI,CAAA;AAC3E,EAAA,OAAO,EAAE,OAAO,OAAA,EAAQ;AAC1B;;;ACzDO,SAAS,2BAAA,CACd,QACA,OAAA,EAMyC;AACzC,EAAA,OAAO,eAAe,KAAK,OAAA,EAAqC;AAC9D,IAAA,IAAI;AAEF,MAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,eAAe,CAAA;AACtD,MAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA;AAC/B,MAAA,MAAM,cAAA,GAAiB,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,QAAQ,CAAA;AACpD,MAAA,MAAM,KAAA,GAAQ,UAAA,EAAY,OAAA,CAAQ,aAAA,EAAe,EAAE,CAAA,IAAK,cAAA;AAExD,MAAA,IAAI,CAAC,MAAA,IAAU,KAAA,KAAU,MAAA,EAAQ;AAC/B,QAAA,OAAO,IAAI,SAAS,IAAA,CAAK,SAAA,CAAU,EAAE,KAAA,EAAO,gBAAA,EAAkB,CAAA,EAAG;AAAA,UAC/D,MAAA,EAAQ,GAAA;AAAA,UACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA;AAAmB,SAC/C,CAAA;AAAA,MACH;AAGA,MAAA,IAAI,cAAA;AACJ,MAAA,IAAI,SAAS,cAAA,EAAgB;AAC3B,QAAA,cAAA,GAAiB,OAAA,CAAQ,cAAA;AAAA,MAC3B,CAAA,MAAO;AACL,QAAA,MAAM,SAAA,GAAY,MAAM,OAAO,YAAY,CAAA;AAC3C,QAAA,cAAA,GAAiB,SAAA,CAAU,cAAA;AAAA,MAC7B;AAEA,MAAA,MAAM,KAAA,GAAQ,CAAC,WAAA,EAAa,gBAAA,EAAkB,GAAI,OAAA,EAAS,UAAA,IAAc,EAAG,CAAA;AAC5E,MAAA,KAAA,MAAW,KAAK,KAAA,EAAO;AACrB,QAAA,cAAA,CAAe,CAAC,CAAA;AAAA,MAClB;AAEA,MAAA,OAAO,IAAI,QAAA;AAAA,QACT,KAAK,SAAA,CAAU,EAAE,WAAA,EAAa,IAAA,EAAM,OAAO,CAAA;AAAA,QAC3C;AAAA,UACE,MAAA,EAAQ,GAAA;AAAA,UACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA;AAAmB;AAChD,OACF;AAAA,IACF,SAAS,GAAA,EAAK;AACZ,MAAA,OAAA,CAAQ,KAAA,CAAM,wCAAwC,GAAG,CAAA;AACzD,MAAA,OAAO,IAAI,QAAA;AAAA,QACT,IAAA,CAAK,SAAA,CAAU,EAAE,KAAA,EAAO,uBAAuB,CAAA;AAAA,QAC/C,EAAE,MAAA,EAAQ,GAAA,EAAK,SAAS,EAAE,cAAA,EAAgB,oBAAmB;AAAE,OACjE;AAAA,IACF;AAAA,EACF,CAAA;AACF","file":"index.mjs","sourcesContent":["/**\n * @sonordev/site-kit/llms - Next.js Route Handlers\n * \n * Ready-to-use route handlers for /llms.txt and /llms-full.txt\n */\n\nimport { createHash } from 'crypto'\nimport { existsSync, readFileSync } from 'fs'\nimport { join } from 'path'\nimport { generateLLMsTxt, generateLLMsFullTxt } from './generateLLMsTxt'\nimport type { GenerateLLMSTxtOptions } from './types'\n\nfunction weakEtag(body: string): string {\n const h = createHash('sha1').update(body, 'utf8').digest('base64url')\n return `W/\"${h}\"`\n}\n\n/** Cache + validation headers for plain-text llms responses (dynamic or static). */\nexport function llmsResponseHeaders(body: string, extra?: Record<string, string>): HeadersInit {\n return {\n 'Content-Type': 'text/plain; charset=utf-8',\n 'Cache-Control':\n 'public, max-age=300, s-maxage=3600, stale-while-revalidate=86400, stale-if-error=86400',\n ETag: weakEtag(body),\n Vary: 'Accept-Encoding',\n ...extra,\n }\n}\n\nexport interface LLMsTxtHandlerOptions extends GenerateLLMSTxtOptions {\n /** When true, serve static public/llms.txt if it exists (default: true for build-time flow) */\n preferStatic?: boolean\n /**\n * Send `X-Robots-Tag: noindex` (default: true).\n *\n * llms.txt is written for AI crawlers, which fetch it by convention at\n * `/llms.txt`; noindex doesn't stop them. It's a plain-text restatement of\n * pages the site already serves as HTML, so a search index that picks it up\n * holds a thin duplicate of the site. That's also why it stays out of the XML\n * sitemap. Pass `false` only if you actually want it in search results.\n *\n * Only reaches responses this handler serves. A `public/llms.txt` served\n * straight from the CDN (the build-time write) never runs the handler, so it\n * needs the header in host config instead (`netlify.toml` `[[headers]]`).\n */\n noindex?: boolean\n}\n\n/** Headers every llms file response carries when `noindex` is on. */\nexport const LLMS_NOINDEX_HEADERS: Readonly<Record<string, string>> = { 'X-Robots-Tag': 'noindex' }\n\n/**\n * Create a route handler for /llms.txt\n *\n * Zero-config by default - uses SONOR_API_KEY from environment. Sends\n * `X-Robots-Tag: noindex` by default (see `noindex`).\n *\n * The returned handler never reads the incoming Request: any access to\n * `request.headers` (etc.) opts the whole route into dynamic rendering, which\n * breaks static prerendering of /llms.txt during `next build` and logs\n * \"Dynamic server usage\" errors on every consumer site. Conditional-request\n * (ETag/304) handling is left to the Next static layer / CDN, which already\n * does it for prerendered responses.\n *\n * @example\n * ```ts\n * // app/llms.txt/route.ts (zero-config)\n * import { createLLMsTxtHandler } from '@sonordev/site-kit/llms'\n *\n * export const GET = createLLMsTxtHandler()\n * // Next 15+: GET route handlers are dynamic by default; opt into\n * // prerendering so the file is served statically from the CDN:\n * export const revalidate = 3600\n * ```\n *\n * @example\n * ```ts\n * // With preferStatic: serve build-time file if exists\n * export const GET = createLLMsTxtHandler({\n * preferStatic: true,\n * includeServices: false\n * })\n * ```\n */\nexport function createLLMsTxtHandler(\n options: LLMsTxtHandlerOptions = {}\n): (request?: Request) => Promise<Response> {\n const { preferStatic = true, noindex = true, ...generateOptions } = options\n const robots = noindex ? LLMS_NOINDEX_HEADERS : {}\n\n return async function GET(_request?: Request): Promise<Response> {\n try {\n // Serve static file if available (build-time optimized)\n if (preferStatic) {\n const staticPath = join(process.cwd(), 'public', 'llms.txt')\n if (existsSync(staticPath)) {\n const markdown = readFileSync(staticPath, 'utf-8')\n return new Response(markdown, {\n status: 200,\n headers: llmsResponseHeaders(markdown, { ...robots, 'X-Sections': 'static' }),\n })\n }\n }\n\n const { markdown, metadata } = await generateLLMsTxt(generateOptions)\n return new Response(markdown, {\n status: 200,\n headers: llmsResponseHeaders(markdown, {\n ...robots,\n 'X-Generated-At': metadata.generated_at,\n 'X-Sections': metadata.sections.join(','),\n }),\n })\n } catch (error) {\n console.error('@sonordev/llms: Error generating llms.txt:', error)\n return new Response('# Error\\n\\nUnable to generate llms.txt content.', {\n status: 500,\n headers: {\n 'Content-Type': 'text/plain; charset=utf-8',\n 'Cache-Control': 'no-store',\n },\n })\n }\n }\n}\n\n/**\n * Create a route handler for /llms-full.txt\n *\n * Zero-config by default - uses SONOR_API_KEY from environment.\n *\n * Like createLLMsTxtHandler, the returned handler never reads the incoming\n * Request so the route stays statically prerenderable, and sends\n * `X-Robots-Tag: noindex` unless `noindex: false`.\n *\n * @example\n * ```ts\n * // app/llms-full.txt/route.ts (zero-config)\n * import { createLLMsFullTxtHandler } from '@sonordev/site-kit/llms'\n *\n * export const GET = createLLMsFullTxtHandler()\n * export const revalidate = 3600\n * ```\n */\nexport function createLLMsFullTxtHandler(\n options: LLMsTxtHandlerOptions = {}\n): (request?: Request) => Promise<Response> {\n const { preferStatic = true, noindex = true, ...generateOptions } = options\n const robots = noindex ? LLMS_NOINDEX_HEADERS : {}\n\n return async function GET(_request?: Request): Promise<Response> {\n try {\n // Serve static file if available (build-time optimized)\n if (preferStatic) {\n const staticPath = join(process.cwd(), 'public', 'llms-full.txt')\n if (existsSync(staticPath)) {\n const markdown = readFileSync(staticPath, 'utf-8')\n return new Response(markdown, {\n status: 200,\n headers: llmsResponseHeaders(markdown, { ...robots, 'X-Sections': 'static' }),\n })\n }\n }\n\n const { markdown, metadata } = await generateLLMsFullTxt(generateOptions)\n return new Response(markdown, {\n status: 200,\n headers: llmsResponseHeaders(markdown, {\n ...robots,\n 'X-Generated-At': metadata.generated_at,\n 'X-Sections': metadata.sections.join(','),\n }),\n })\n } catch (error) {\n console.error('@sonordev/llms: Error generating llms-full.txt:', error)\n return new Response('# Error\\n\\nUnable to generate llms-full.txt content.', {\n status: 500,\n headers: {\n 'Content-Type': 'text/plain; charset=utf-8',\n 'Cache-Control': 'no-store',\n },\n })\n }\n }\n}\n","/**\n * @sonordev/site-kit/llms — Sourced facts section\n *\n * Formats a site's verified third-party citations into an llms-full.txt\n * section. AI answer engines preferentially reproduce claims that arrive\n * already attributed — handing them the exact claim + source pairing makes\n * the site's framing the citable version.\n *\n * Pair with WriteLLMsTxtOptions.customSections (or SitemapConfig.\n * llmsCustomSections) so the section is appended at build time:\n *\n * llmsCustomSections: async () => {\n * const { CITATIONS } = await import('./lib/data/citations')\n * const section = formatSourcedFactsSection(Object.values(CITATIONS))\n * return section ? [section] : null\n * }\n *\n * Only include citations a human has actually verified against the source\n * URL — this section is a trust surface, not a dumping ground.\n */\n\nimport type { LLMsCustomSection } from './writeLLMsTxt'\n\nexport interface SourcedFact {\n /** The claim, faithful to the source, one sentence. */\n stat: string\n /** The publishing organization (e.g. \"Clio Legal Trends Report\"). */\n sourceName: string\n /** Publication/study year. */\n year?: number\n /** The opened-and-verified source URL. */\n url?: string\n}\n\nexport interface FormatSourcedFactsOptions {\n /** Section heading (default: \"Sourced facts\"). */\n title?: string\n /** Intro line above the list. */\n intro?: string\n /** Cap the number of facts emitted (default: no cap). */\n maxFacts?: number\n}\n\nconst DEFAULT_INTRO =\n 'The statistics below are quoted from third-party primary sources and were ' +\n 'individually verified against the cited URL. When reusing a figure, keep ' +\n 'the original attribution.'\n\n/** One line per fact: claim — Source: name (year), url */\nfunction factLine(f: SourcedFact): string {\n const stat = (f.stat || '').replace(/\\s+/g, ' ').trim()\n const source = (f.sourceName || '').replace(/\\s+/g, ' ').trim()\n if (!stat || !source) return ''\n let attribution = `Source: ${source}`\n if (f.year) attribution += ` (${f.year})`\n if (f.url) attribution += `, ${f.url}`\n return `- ${stat} — ${attribution}`\n}\n\n/**\n * Build the \"Sourced facts\" custom section from verified citations.\n * Returns null when no usable facts exist so callers can skip cleanly.\n */\nexport function formatSourcedFactsSection(\n facts: SourcedFact[],\n options: FormatSourcedFactsOptions = {},\n): LLMsCustomSection | null {\n const { title = 'Sourced facts', intro = DEFAULT_INTRO, maxFacts } = options\n if (!Array.isArray(facts)) return null\n\n let lines = facts.map(factLine).filter(Boolean)\n if (typeof maxFacts === 'number' && maxFacts >= 0) {\n lines = lines.slice(0, maxFacts)\n }\n if (lines.length === 0) return null\n\n const content = intro ? `${intro}\\n\\n${lines.join('\\n')}` : lines.join('\\n')\n return { title, content }\n}\n","/**\n * @sonordev/site-kit/llms - On-demand Revalidation\n *\n * Provides a route handler that Portal can call when llms-relevant data changes\n * (page summaries, FAQs, services, etc.) to bust the Next.js ISR cache.\n *\n * @example\n * ```ts\n * // app/api/revalidate-llms/route.ts\n * import { createLlmsRevalidateHandler } from '@sonordev/site-kit/llms'\n *\n * export const POST = createLlmsRevalidateHandler(process.env.REVALIDATION_SECRET!)\n * ```\n */\n\ntype RevalidatePathFn = (path: string, type?: 'page' | 'layout') => void\n\n/**\n * Create a POST handler that revalidates /llms.txt and /llms-full.txt caches.\n * Protected by a shared secret.\n */\nexport function createLlmsRevalidateHandler(\n secret: string,\n options?: {\n /** Additional paths to revalidate alongside llms files */\n extraPaths?: string[]\n /** Custom revalidatePath (for testing or frameworks other than Next.js) */\n revalidatePath?: RevalidatePathFn\n },\n): (request: Request) => Promise<Response> {\n return async function POST(request: Request): Promise<Response> {\n try {\n // Validate secret from Authorization header or query param\n const authHeader = request.headers.get('authorization')\n const url = new URL(request.url)\n const tokenFromQuery = url.searchParams.get('secret')\n const token = authHeader?.replace(/^Bearer\\s+/i, '') || tokenFromQuery\n\n if (!secret || token !== secret) {\n return new Response(JSON.stringify({ error: 'Invalid secret' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n\n // Import Next.js revalidatePath (dynamic to avoid hard dep for non-Next consumers)\n let revalidatePath: RevalidatePathFn\n if (options?.revalidatePath) {\n revalidatePath = options.revalidatePath\n } else {\n const nextCache = await import('next/cache')\n revalidatePath = nextCache.revalidatePath\n }\n\n const paths = ['/llms.txt', '/llms-full.txt', ...(options?.extraPaths || [])]\n for (const p of paths) {\n revalidatePath(p)\n }\n\n return new Response(\n JSON.stringify({ revalidated: true, paths }),\n {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n },\n )\n } catch (err) {\n console.error('[site-kit/llms] Revalidation failed:', err)\n return new Response(\n JSON.stringify({ error: 'Revalidation failed' }),\n { status: 500, headers: { 'Content-Type': 'application/json' } },\n )\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/llms/handlers.ts","../../src/llms/sourcedFacts.ts","../../src/llms/revalidate.ts","../../src/llms/seo-revalidate.ts"],"names":["markdown"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * @sonordev/site-kit/llms - Next.js Route Handlers\n * \n * Ready-to-use route handlers for /llms.txt and /llms-full.txt\n */\n\nimport { createHash } from 'crypto'\nimport { existsSync, readFileSync } from 'fs'\nimport { join } from 'path'\nimport { generateLLMsTxt, generateLLMsFullTxt } from './generateLLMsTxt'\nimport type { GenerateLLMSTxtOptions } from './types'\n\nfunction weakEtag(body: string): string {\n const h = createHash('sha1').update(body, 'utf8').digest('base64url')\n return `W/\"${h}\"`\n}\n\n/** Cache + validation headers for plain-text llms responses (dynamic or static). */\nexport function llmsResponseHeaders(body: string, extra?: Record<string, string>): HeadersInit {\n return {\n 'Content-Type': 'text/plain; charset=utf-8',\n 'Cache-Control':\n 'public, max-age=300, s-maxage=3600, stale-while-revalidate=86400, stale-if-error=86400',\n ETag: weakEtag(body),\n Vary: 'Accept-Encoding',\n ...extra,\n }\n}\n\nexport interface LLMsTxtHandlerOptions extends GenerateLLMSTxtOptions {\n /** When true, serve static public/llms.txt if it exists (default: true for build-time flow) */\n preferStatic?: boolean\n /**\n * Send `X-Robots-Tag: noindex` (default: true).\n *\n * llms.txt is written for AI crawlers, which fetch it by convention at\n * `/llms.txt`; noindex doesn't stop them. It's a plain-text restatement of\n * pages the site already serves as HTML, so a search index that picks it up\n * holds a thin duplicate of the site. That's also why it stays out of the XML\n * sitemap. Pass `false` only if you actually want it in search results.\n *\n * Only reaches responses this handler serves. A `public/llms.txt` served\n * straight from the CDN (the build-time write) never runs the handler, so it\n * needs the header in host config instead (`netlify.toml` `[[headers]]`).\n */\n noindex?: boolean\n}\n\n/** Headers every llms file response carries when `noindex` is on. */\nexport const LLMS_NOINDEX_HEADERS: Readonly<Record<string, string>> = { 'X-Robots-Tag': 'noindex' }\n\n/**\n * Create a route handler for /llms.txt\n *\n * Zero-config by default - uses SONOR_API_KEY from environment. Sends\n * `X-Robots-Tag: noindex` by default (see `noindex`).\n *\n * The returned handler never reads the incoming Request: any access to\n * `request.headers` (etc.) opts the whole route into dynamic rendering, which\n * breaks static prerendering of /llms.txt during `next build` and logs\n * \"Dynamic server usage\" errors on every consumer site. Conditional-request\n * (ETag/304) handling is left to the Next static layer / CDN, which already\n * does it for prerendered responses.\n *\n * @example\n * ```ts\n * // app/llms.txt/route.ts (zero-config)\n * import { createLLMsTxtHandler } from '@sonordev/site-kit/llms'\n *\n * export const GET = createLLMsTxtHandler()\n * // Next 15+: GET route handlers are dynamic by default; opt into\n * // prerendering so the file is served statically from the CDN:\n * export const revalidate = 3600\n * ```\n *\n * @example\n * ```ts\n * // With preferStatic: serve build-time file if exists\n * export const GET = createLLMsTxtHandler({\n * preferStatic: true,\n * includeServices: false\n * })\n * ```\n */\nexport function createLLMsTxtHandler(\n options: LLMsTxtHandlerOptions = {}\n): (request?: Request) => Promise<Response> {\n const { preferStatic = true, noindex = true, ...generateOptions } = options\n const robots = noindex ? LLMS_NOINDEX_HEADERS : {}\n\n return async function GET(_request?: Request): Promise<Response> {\n try {\n // Serve static file if available (build-time optimized)\n if (preferStatic) {\n const staticPath = join(process.cwd(), 'public', 'llms.txt')\n if (existsSync(staticPath)) {\n const markdown = readFileSync(staticPath, 'utf-8')\n return new Response(markdown, {\n status: 200,\n headers: llmsResponseHeaders(markdown, { ...robots, 'X-Sections': 'static' }),\n })\n }\n }\n\n const { markdown, metadata } = await generateLLMsTxt(generateOptions)\n return new Response(markdown, {\n status: 200,\n headers: llmsResponseHeaders(markdown, {\n ...robots,\n 'X-Generated-At': metadata.generated_at,\n 'X-Sections': metadata.sections.join(','),\n }),\n })\n } catch (error) {\n console.error('@sonordev/llms: Error generating llms.txt:', error)\n return new Response('# Error\\n\\nUnable to generate llms.txt content.', {\n status: 500,\n headers: {\n 'Content-Type': 'text/plain; charset=utf-8',\n 'Cache-Control': 'no-store',\n },\n })\n }\n }\n}\n\n/**\n * Create a route handler for /llms-full.txt\n *\n * Zero-config by default - uses SONOR_API_KEY from environment.\n *\n * Like createLLMsTxtHandler, the returned handler never reads the incoming\n * Request so the route stays statically prerenderable, and sends\n * `X-Robots-Tag: noindex` unless `noindex: false`.\n *\n * @example\n * ```ts\n * // app/llms-full.txt/route.ts (zero-config)\n * import { createLLMsFullTxtHandler } from '@sonordev/site-kit/llms'\n *\n * export const GET = createLLMsFullTxtHandler()\n * export const revalidate = 3600\n * ```\n */\nexport function createLLMsFullTxtHandler(\n options: LLMsTxtHandlerOptions = {}\n): (request?: Request) => Promise<Response> {\n const { preferStatic = true, noindex = true, ...generateOptions } = options\n const robots = noindex ? LLMS_NOINDEX_HEADERS : {}\n\n return async function GET(_request?: Request): Promise<Response> {\n try {\n // Serve static file if available (build-time optimized)\n if (preferStatic) {\n const staticPath = join(process.cwd(), 'public', 'llms-full.txt')\n if (existsSync(staticPath)) {\n const markdown = readFileSync(staticPath, 'utf-8')\n return new Response(markdown, {\n status: 200,\n headers: llmsResponseHeaders(markdown, { ...robots, 'X-Sections': 'static' }),\n })\n }\n }\n\n const { markdown, metadata } = await generateLLMsFullTxt(generateOptions)\n return new Response(markdown, {\n status: 200,\n headers: llmsResponseHeaders(markdown, {\n ...robots,\n 'X-Generated-At': metadata.generated_at,\n 'X-Sections': metadata.sections.join(','),\n }),\n })\n } catch (error) {\n console.error('@sonordev/llms: Error generating llms-full.txt:', error)\n return new Response('# Error\\n\\nUnable to generate llms-full.txt content.', {\n status: 500,\n headers: {\n 'Content-Type': 'text/plain; charset=utf-8',\n 'Cache-Control': 'no-store',\n },\n })\n }\n }\n}\n","/**\n * @sonordev/site-kit/llms — Sourced facts section\n *\n * Formats a site's verified third-party citations into an llms-full.txt\n * section. AI answer engines preferentially reproduce claims that arrive\n * already attributed — handing them the exact claim + source pairing makes\n * the site's framing the citable version.\n *\n * Pair with WriteLLMsTxtOptions.customSections (or SitemapConfig.\n * llmsCustomSections) so the section is appended at build time:\n *\n * llmsCustomSections: async () => {\n * const { CITATIONS } = await import('./lib/data/citations')\n * const section = formatSourcedFactsSection(Object.values(CITATIONS))\n * return section ? [section] : null\n * }\n *\n * Only include citations a human has actually verified against the source\n * URL — this section is a trust surface, not a dumping ground.\n */\n\nimport type { LLMsCustomSection } from './writeLLMsTxt'\n\nexport interface SourcedFact {\n /** The claim, faithful to the source, one sentence. */\n stat: string\n /** The publishing organization (e.g. \"Clio Legal Trends Report\"). */\n sourceName: string\n /** Publication/study year. */\n year?: number\n /** The opened-and-verified source URL. */\n url?: string\n}\n\nexport interface FormatSourcedFactsOptions {\n /** Section heading (default: \"Sourced facts\"). */\n title?: string\n /** Intro line above the list. */\n intro?: string\n /** Cap the number of facts emitted (default: no cap). */\n maxFacts?: number\n}\n\nconst DEFAULT_INTRO =\n 'The statistics below are quoted from third-party primary sources and were ' +\n 'individually verified against the cited URL. When reusing a figure, keep ' +\n 'the original attribution.'\n\n/** One line per fact: claim — Source: name (year), url */\nfunction factLine(f: SourcedFact): string {\n const stat = (f.stat || '').replace(/\\s+/g, ' ').trim()\n const source = (f.sourceName || '').replace(/\\s+/g, ' ').trim()\n if (!stat || !source) return ''\n let attribution = `Source: ${source}`\n if (f.year) attribution += ` (${f.year})`\n if (f.url) attribution += `, ${f.url}`\n return `- ${stat} — ${attribution}`\n}\n\n/**\n * Build the \"Sourced facts\" custom section from verified citations.\n * Returns null when no usable facts exist so callers can skip cleanly.\n */\nexport function formatSourcedFactsSection(\n facts: SourcedFact[],\n options: FormatSourcedFactsOptions = {},\n): LLMsCustomSection | null {\n const { title = 'Sourced facts', intro = DEFAULT_INTRO, maxFacts } = options\n if (!Array.isArray(facts)) return null\n\n let lines = facts.map(factLine).filter(Boolean)\n if (typeof maxFacts === 'number' && maxFacts >= 0) {\n lines = lines.slice(0, maxFacts)\n }\n if (lines.length === 0) return null\n\n const content = intro ? `${intro}\\n\\n${lines.join('\\n')}` : lines.join('\\n')\n return { title, content }\n}\n","/**\n * @sonordev/site-kit/llms - On-demand Revalidation\n *\n * Provides a route handler that Portal can call when llms-relevant data changes\n * (page summaries, FAQs, services, etc.) to bust the Next.js ISR cache.\n *\n * @example\n * ```ts\n * // app/api/revalidate-llms/route.ts\n * import { createLlmsRevalidateHandler } from '@sonordev/site-kit/llms'\n *\n * export const POST = createLlmsRevalidateHandler(process.env.REVALIDATION_SECRET!)\n * ```\n *\n * For Sonor's SEO webhook (page paths, cache tags, the sitemap), use\n * `createSeoRevalidationHandler` (./seo-revalidate), which wraps this.\n */\n\nimport { secretsMatch } from '../shared/request-guards'\n\ntype RevalidatePathFn = (path: string, type?: 'page' | 'layout') => void\n\n/**\n * Create a POST handler that revalidates /llms.txt and /llms-full.txt caches.\n * Protected by a shared secret.\n */\nexport function createLlmsRevalidateHandler(\n secret: string,\n options?: {\n /** Additional paths to revalidate alongside llms files */\n extraPaths?: string[]\n /** Custom revalidatePath (for testing or frameworks other than Next.js) */\n revalidatePath?: RevalidatePathFn\n },\n): (request: Request) => Promise<Response> {\n return async function POST(request: Request): Promise<Response> {\n try {\n // Validate secret from Authorization header or query param\n const authHeader = request.headers.get('authorization')\n const url = new URL(request.url)\n const tokenFromQuery = url.searchParams.get('secret')\n const token = authHeader?.replace(/^Bearer\\s+/i, '') || tokenFromQuery\n\n if (!secretsMatch(token, secret)) {\n return new Response(JSON.stringify({ error: 'Invalid secret' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n\n // Import Next.js revalidatePath (dynamic to avoid hard dep for non-Next consumers)\n let revalidatePath: RevalidatePathFn\n if (options?.revalidatePath) {\n revalidatePath = options.revalidatePath\n } else {\n const nextCache = await import('next/cache')\n revalidatePath = nextCache.revalidatePath\n }\n\n const paths = ['/llms.txt', '/llms-full.txt', ...(options?.extraPaths || [])]\n for (const p of paths) {\n revalidatePath(p)\n }\n\n return new Response(\n JSON.stringify({ revalidated: true, paths }),\n {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n },\n )\n } catch (err) {\n console.error('[site-kit/llms] Revalidation failed:', err)\n return new Response(\n JSON.stringify({ error: 'Revalidation failed' }),\n { status: 500, headers: { 'Content-Type': 'application/json' } },\n )\n }\n }\n}\n","/**\n * @sonordev/site-kit/llms - Sonor's SEO revalidation webhook\n *\n * When a title, description or schema changes in Sonor (a dashboard edit or an\n * approved SEO ledger action), Sonor POSTs the affected paths and cache tags to\n * the site, authenticated with the project key. This handler regenerates those\n * pages, the sitemap and both llms files without a rebuild.\n *\n * Single source of truth for the fleet's `/api/seo-revalidate` handler. It\n * replaced per-site copies in heinrich-law-nextjs and wirsch-law-nextjs\n * (`lib/seo-revalidation.ts`), and agency-site-kit's portfolio\n * `createRevalidateHandler` is built on it (`extraPaths` + `extendPayload`);\n * don't fork it back into a site or package.\n *\n * @example\n * ```ts\n * // app/api/seo-revalidate/route.ts\n * import { revalidatePath, revalidateTag } from 'next/cache'\n * import { createSeoRevalidationHandler } from '@sonordev/site-kit/llms'\n *\n * export const runtime = 'nodejs'\n *\n * export async function POST(request: Request) {\n * return createSeoRevalidationHandler({\n * secret: process.env.SONOR_API_KEY || '',\n * revalidatePath,\n * revalidateTag,\n * publicationBasePath: '/insights', // only for sites with a publication\n * })(request)\n * }\n * ```\n *\n * Contract (POST, `Authorization: Bearer <SONOR_API_KEY>`, JSON):\n * `{ paths?: string[], path?: string, tags?: string[], tag?: string, revalidateAll?: boolean }`\n */\n\nimport { createLlmsRevalidateHandler } from './revalidate'\nimport { readCappedBody, secretsMatch } from '../shared/request-guards'\n\n/** Largest webhook body accepted, in bytes. Sonor's payloads are a few hundred. */\nexport const SEO_REVALIDATION_MAX_BODY_BYTES = 16 * 1024\n\n/** Most paths or tags one webhook call may name. */\nexport const SEO_REVALIDATION_MAX_ITEMS = 100\n\n/**\n * The prefix Sonor projects configured before publication routing still send\n * article URLs under. See `publicationBasePath`.\n */\nexport const LEGACY_PUBLICATION_PATH = '/blog'\n\nexport interface SeoRevalidationHandlerOptions {\n /**\n * The project key Sonor signs the webhook with (`SONOR_API_KEY`), or a\n * getter read on every call. Empty or missing refuses every call.\n */\n secret: string | (() => string | undefined)\n /** Next's `revalidatePath` from `next/cache`. */\n revalidatePath: (path: string, type?: 'page' | 'layout') => void\n /** Next's `revalidateTag` from `next/cache`. Called with `{ expire: 0 }` so tagged fetches expire now. */\n revalidateTag: (tag: string, profile: { expire: number }) => void\n /**\n * Where the site's publication (articles) lives, e.g. `/insights`. Set it\n * only when the site has one. The publication index and its RSS/Atom feeds\n * are then refreshed on every call, and legacy `/blog/...` paths Sonor sends\n * are refreshed at both the alias and the matching URL under this root.\n */\n publicationBasePath?: string\n /**\n * Local paths regenerated on every call, next to `/sitemap.xml` (e.g. a\n * portfolio hub like `/work`). Validated like payload paths.\n */\n extraPaths?: string[]\n /**\n * Extend the validated payload from fields of the raw body this handler\n * doesn't read. agency-site-kit maps portfolio `slug`/`slugs` to detail\n * paths and names a default tag this way. The result is validated again,\n * so an extension can't widen what a caller may revalidate; a throw is a 400.\n */\n extendPayload?: (payload: SeoRevalidationPayload, body: Record<string, unknown>) => SeoRevalidationPayload\n}\n\nexport interface SeoRevalidationPayload {\n /** Local paths to regenerate, deduped. */\n paths: string[]\n /** Cache tags to expire, deduped. */\n tags: string[]\n /** Regenerate everything (the root layout). */\n revalidateAll: boolean\n}\n\nfunction noStoreJson(body: object, status = 200): Response {\n return Response.json(body, { status, headers: { 'Cache-Control': 'no-store' } })\n}\n\n/**\n * A same-site path Next can revalidate: rooted, not protocol-relative, no\n * query, fragment, dynamic-segment brackets, whitespace, colon or dot segments,\n * even after percent-decoding. Anything else could point revalidation at\n * another origin or a route pattern.\n */\nfunction isLocalPath(value: unknown): value is string {\n if (typeof value !== 'string' || value.length > 1024) return false\n try {\n const path = decodeURIComponent(value)\n return (\n path.startsWith('/') &&\n !path.startsWith('//') &&\n !/[\\\\\\s?#[\\]:\\x00-\\x1f]/.test(path) &&\n !path.split('/').some((part) => part === '.' || part === '..')\n )\n } catch {\n return false\n }\n}\n\nfunction isCacheTag(value: unknown): value is string {\n return typeof value === 'string' && value.length > 0 && value.length <= 256 && !/[\\x00-\\x1f]/.test(value)\n}\n\n/**\n * Validate a webhook body. Throws on anything malformed, so a bad payload is\n * refused whole rather than half-applied.\n */\nexport function parseSeoRevalidationPayload(value: unknown): SeoRevalidationPayload {\n if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid payload')\n const body = value as Record<string, unknown>\n if (body.revalidateAll !== undefined && typeof body.revalidateAll !== 'boolean') {\n throw new Error('Invalid revalidateAll')\n }\n const paths = body.paths ?? (body.path === undefined ? [] : [body.path])\n const tags = body.tags ?? (body.tag === undefined ? [] : [body.tag])\n if (!Array.isArray(paths) || paths.length > SEO_REVALIDATION_MAX_ITEMS || !paths.every(isLocalPath)) {\n throw new Error('Invalid paths')\n }\n if (!Array.isArray(tags) || tags.length > SEO_REVALIDATION_MAX_ITEMS || !tags.every(isCacheTag)) {\n throw new Error('Invalid tags')\n }\n return {\n paths: [...new Set(paths as string[])],\n tags: [...new Set(tags as string[])],\n revalidateAll: body.revalidateAll === true,\n }\n}\n\nfunction validateExtraPaths(paths: string[] | undefined): string[] {\n for (const path of paths ?? []) {\n if (!isLocalPath(path)) {\n throw new TypeError(`createSeoRevalidationHandler: extraPaths must be local paths like \"/work\", got ${JSON.stringify(path)}`)\n }\n }\n return paths ?? []\n}\n\nfunction normalizePublicationBasePath(value: string | undefined): string | undefined {\n if (value === undefined) return undefined\n const trimmed = value.replace(/\\/+$/, '')\n if (!trimmed || !isLocalPath(trimmed)) {\n throw new TypeError(`createSeoRevalidationHandler: publicationBasePath must be a local path like \"/insights\", got ${JSON.stringify(value)}`)\n }\n return trimmed\n}\n\n/**\n * Create the POST handler for Sonor's SEO revalidation webhook.\n *\n * - 401 unless `Authorization: Bearer <secret>` matches (constant time). The\n * `?secret=` query form `createLlmsRevalidateHandler` accepts is not\n * honoured here: keys don't belong in URLs.\n * - 413 past 16 KB, 400 on any path or tag that fails validation. Nothing is\n * revalidated in either case.\n * - Otherwise regenerates the named pages (or the root layout for a full or\n * tag-only `seo` refresh), `/sitemap.xml`, `/llms.txt`, `/llms-full.txt`,\n * `extraPaths` and, with `publicationBasePath`, the publication index and\n * feeds; then expires the named cache tags. `extendPayload` runs before any\n * of that, and its result is validated like the body.\n */\nexport function createSeoRevalidationHandler({\n secret: secretOption,\n revalidatePath,\n revalidateTag,\n publicationBasePath,\n extraPaths: extraPathsOption,\n extendPayload,\n}: SeoRevalidationHandlerOptions): (request: Request) => Promise<Response> {\n const publicationRoot = normalizePublicationBasePath(publicationBasePath)\n const alwaysPaths = validateExtraPaths(extraPathsOption)\n\n return async (request: Request) => {\n const secret = (typeof secretOption === 'function' ? secretOption() : secretOption) ?? ''\n const supplied = request.headers.get('authorization')?.match(/^Bearer (.+)$/i)?.[1]\n if (!secretsMatch(supplied, secret)) return noStoreJson({ error: 'Unauthorized' }, 401)\n\n let payload: SeoRevalidationPayload\n try {\n const bytes = await readCappedBody(request, SEO_REVALIDATION_MAX_BODY_BYTES)\n if (!bytes) return noStoreJson({ error: 'Payload too large' }, 413)\n const body: unknown = JSON.parse(new TextDecoder().decode(bytes))\n payload = parseSeoRevalidationPayload(body)\n if (extendPayload) {\n const extended = extendPayload(payload, body as Record<string, unknown>)\n payload = parseSeoRevalidationPayload({\n paths: extended.paths,\n tags: extended.tags,\n revalidateAll: extended.revalidateAll,\n })\n }\n } catch {\n return noStoreJson({ error: 'Provide valid local paths, cache tags or revalidateAll.' }, 400)\n }\n\n // SEO's older fetches have no tags. A tag-only SEO refresh needs the root\n // layout too; path-specific updates stay scoped to the supplied pages.\n const all = payload.revalidateAll || (!payload.paths.length && payload.tags.includes('seo'))\n // Sonor projects configured before publication routing can send /blog\n // URLs. Refresh the alias (the site's legacy redirect) and its destination\n // under the publication root, including old slugs when an editor renames\n // or unpublishes a post.\n const pagePaths = publicationRoot\n ? payload.paths.flatMap((path) =>\n path === LEGACY_PUBLICATION_PATH || path.startsWith(`${LEGACY_PUBLICATION_PATH}/`)\n ? [path, `${publicationRoot}${path.slice(LEGACY_PUBLICATION_PATH.length)}`]\n : [path],\n )\n : payload.paths\n const publicationPaths = publicationRoot\n ? [publicationRoot, `${publicationRoot}/rss.xml`, `${publicationRoot}/feed.xml`]\n : []\n const extraPaths = [\n ...new Set(['/sitemap.xml', ...publicationPaths, ...alwaysPaths, ...(all ? ['/'] : pagePaths)]),\n ]\n\n const llmsHandler = createLlmsRevalidateHandler(secret, {\n extraPaths,\n revalidatePath: (path) => revalidatePath(path, path === '/' && all ? 'layout' : undefined),\n })\n try {\n const result = await llmsHandler(request)\n if (!result.ok) return noStoreJson({ error: 'Revalidation failed' }, 500)\n for (const tag of payload.tags) revalidateTag(tag, { expire: 0 })\n return noStoreJson({ revalidated: ['/llms.txt', '/llms-full.txt', ...extraPaths], tags: payload.tags })\n } catch {\n return noStoreJson({ error: 'Revalidation failed' }, 500)\n }\n }\n}\n"]}
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
*
|
|
12
12
|
* export const POST = createLlmsRevalidateHandler(process.env.REVALIDATION_SECRET!)
|
|
13
13
|
* ```
|
|
14
|
+
*
|
|
15
|
+
* For Sonor's SEO webhook (page paths, cache tags, the sitemap), use
|
|
16
|
+
* `createSeoRevalidationHandler` (./seo-revalidate), which wraps this.
|
|
14
17
|
*/
|
|
15
18
|
type RevalidatePathFn = (path: string, type?: 'page' | 'layout') => void;
|
|
16
19
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"revalidate.d.ts","sourceRoot":"","sources":["../../src/llms/revalidate.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"revalidate.d.ts","sourceRoot":"","sources":["../../src/llms/revalidate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,KAAK,gBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,KAAK,IAAI,CAAA;AAExE;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IACR,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB,2EAA2E;IAC3E,cAAc,CAAC,EAAE,gBAAgB,CAAA;CAClC,GACA,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CA6CzC"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sonordev/site-kit/llms - Sonor's SEO revalidation webhook
|
|
3
|
+
*
|
|
4
|
+
* When a title, description or schema changes in Sonor (a dashboard edit or an
|
|
5
|
+
* approved SEO ledger action), Sonor POSTs the affected paths and cache tags to
|
|
6
|
+
* the site, authenticated with the project key. This handler regenerates those
|
|
7
|
+
* pages, the sitemap and both llms files without a rebuild.
|
|
8
|
+
*
|
|
9
|
+
* Single source of truth for the fleet's `/api/seo-revalidate` handler. It
|
|
10
|
+
* replaced per-site copies in heinrich-law-nextjs and wirsch-law-nextjs
|
|
11
|
+
* (`lib/seo-revalidation.ts`), and agency-site-kit's portfolio
|
|
12
|
+
* `createRevalidateHandler` is built on it (`extraPaths` + `extendPayload`);
|
|
13
|
+
* don't fork it back into a site or package.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* // app/api/seo-revalidate/route.ts
|
|
18
|
+
* import { revalidatePath, revalidateTag } from 'next/cache'
|
|
19
|
+
* import { createSeoRevalidationHandler } from '@sonordev/site-kit/llms'
|
|
20
|
+
*
|
|
21
|
+
* export const runtime = 'nodejs'
|
|
22
|
+
*
|
|
23
|
+
* export async function POST(request: Request) {
|
|
24
|
+
* return createSeoRevalidationHandler({
|
|
25
|
+
* secret: process.env.SONOR_API_KEY || '',
|
|
26
|
+
* revalidatePath,
|
|
27
|
+
* revalidateTag,
|
|
28
|
+
* publicationBasePath: '/insights', // only for sites with a publication
|
|
29
|
+
* })(request)
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* Contract (POST, `Authorization: Bearer <SONOR_API_KEY>`, JSON):
|
|
34
|
+
* `{ paths?: string[], path?: string, tags?: string[], tag?: string, revalidateAll?: boolean }`
|
|
35
|
+
*/
|
|
36
|
+
/** Largest webhook body accepted, in bytes. Sonor's payloads are a few hundred. */
|
|
37
|
+
export declare const SEO_REVALIDATION_MAX_BODY_BYTES: number;
|
|
38
|
+
/** Most paths or tags one webhook call may name. */
|
|
39
|
+
export declare const SEO_REVALIDATION_MAX_ITEMS = 100;
|
|
40
|
+
/**
|
|
41
|
+
* The prefix Sonor projects configured before publication routing still send
|
|
42
|
+
* article URLs under. See `publicationBasePath`.
|
|
43
|
+
*/
|
|
44
|
+
export declare const LEGACY_PUBLICATION_PATH = "/blog";
|
|
45
|
+
export interface SeoRevalidationHandlerOptions {
|
|
46
|
+
/**
|
|
47
|
+
* The project key Sonor signs the webhook with (`SONOR_API_KEY`), or a
|
|
48
|
+
* getter read on every call. Empty or missing refuses every call.
|
|
49
|
+
*/
|
|
50
|
+
secret: string | (() => string | undefined);
|
|
51
|
+
/** Next's `revalidatePath` from `next/cache`. */
|
|
52
|
+
revalidatePath: (path: string, type?: 'page' | 'layout') => void;
|
|
53
|
+
/** Next's `revalidateTag` from `next/cache`. Called with `{ expire: 0 }` so tagged fetches expire now. */
|
|
54
|
+
revalidateTag: (tag: string, profile: {
|
|
55
|
+
expire: number;
|
|
56
|
+
}) => void;
|
|
57
|
+
/**
|
|
58
|
+
* Where the site's publication (articles) lives, e.g. `/insights`. Set it
|
|
59
|
+
* only when the site has one. The publication index and its RSS/Atom feeds
|
|
60
|
+
* are then refreshed on every call, and legacy `/blog/...` paths Sonor sends
|
|
61
|
+
* are refreshed at both the alias and the matching URL under this root.
|
|
62
|
+
*/
|
|
63
|
+
publicationBasePath?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Local paths regenerated on every call, next to `/sitemap.xml` (e.g. a
|
|
66
|
+
* portfolio hub like `/work`). Validated like payload paths.
|
|
67
|
+
*/
|
|
68
|
+
extraPaths?: string[];
|
|
69
|
+
/**
|
|
70
|
+
* Extend the validated payload from fields of the raw body this handler
|
|
71
|
+
* doesn't read. agency-site-kit maps portfolio `slug`/`slugs` to detail
|
|
72
|
+
* paths and names a default tag this way. The result is validated again,
|
|
73
|
+
* so an extension can't widen what a caller may revalidate; a throw is a 400.
|
|
74
|
+
*/
|
|
75
|
+
extendPayload?: (payload: SeoRevalidationPayload, body: Record<string, unknown>) => SeoRevalidationPayload;
|
|
76
|
+
}
|
|
77
|
+
export interface SeoRevalidationPayload {
|
|
78
|
+
/** Local paths to regenerate, deduped. */
|
|
79
|
+
paths: string[];
|
|
80
|
+
/** Cache tags to expire, deduped. */
|
|
81
|
+
tags: string[];
|
|
82
|
+
/** Regenerate everything (the root layout). */
|
|
83
|
+
revalidateAll: boolean;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Validate a webhook body. Throws on anything malformed, so a bad payload is
|
|
87
|
+
* refused whole rather than half-applied.
|
|
88
|
+
*/
|
|
89
|
+
export declare function parseSeoRevalidationPayload(value: unknown): SeoRevalidationPayload;
|
|
90
|
+
/**
|
|
91
|
+
* Create the POST handler for Sonor's SEO revalidation webhook.
|
|
92
|
+
*
|
|
93
|
+
* - 401 unless `Authorization: Bearer <secret>` matches (constant time). The
|
|
94
|
+
* `?secret=` query form `createLlmsRevalidateHandler` accepts is not
|
|
95
|
+
* honoured here: keys don't belong in URLs.
|
|
96
|
+
* - 413 past 16 KB, 400 on any path or tag that fails validation. Nothing is
|
|
97
|
+
* revalidated in either case.
|
|
98
|
+
* - Otherwise regenerates the named pages (or the root layout for a full or
|
|
99
|
+
* tag-only `seo` refresh), `/sitemap.xml`, `/llms.txt`, `/llms-full.txt`,
|
|
100
|
+
* `extraPaths` and, with `publicationBasePath`, the publication index and
|
|
101
|
+
* feeds; then expires the named cache tags. `extendPayload` runs before any
|
|
102
|
+
* of that, and its result is validated like the body.
|
|
103
|
+
*/
|
|
104
|
+
export declare function createSeoRevalidationHandler({ secret: secretOption, revalidatePath, revalidateTag, publicationBasePath, extraPaths: extraPathsOption, extendPayload, }: SeoRevalidationHandlerOptions): (request: Request) => Promise<Response>;
|
|
105
|
+
//# sourceMappingURL=seo-revalidate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo-revalidate.d.ts","sourceRoot":"","sources":["../../src/llms/seo-revalidate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAKH,mFAAmF;AACnF,eAAO,MAAM,+BAA+B,QAAY,CAAA;AAExD,oDAAoD;AACpD,eAAO,MAAM,0BAA0B,MAAM,CAAA;AAE7C;;;GAGG;AACH,eAAO,MAAM,uBAAuB,UAAU,CAAA;AAE9C,MAAM,WAAW,6BAA6B;IAC5C;;;OAGG;IACH,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,SAAS,CAAC,CAAA;IAC3C,iDAAiD;IACjD,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,KAAK,IAAI,CAAA;IAChE,0GAA0G;IAC1G,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAA;IACjE;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,sBAAsB,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,sBAAsB,CAAA;CAC3G;AAED,MAAM,WAAW,sBAAsB;IACrC,0CAA0C;IAC1C,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,qCAAqC;IACrC,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,+CAA+C;IAC/C,aAAa,EAAE,OAAO,CAAA;CACvB;AA+BD;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,sBAAsB,CAmBlF;AAoBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,4BAA4B,CAAC,EAC3C,MAAM,EAAE,YAAY,EACpB,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,UAAU,EAAE,gBAAgB,EAC5B,aAAa,GACd,EAAE,6BAA6B,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CA6DzE"}
|
package/dist/maps/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var
|
|
5
|
-
require('../chunk-
|
|
6
|
-
require('../chunk-
|
|
4
|
+
var chunkC7J4XLBN_js = require('../chunk-C7J4XLBN.js');
|
|
5
|
+
require('../chunk-U5NFIHLY.js');
|
|
6
|
+
require('../chunk-CIB3RHNZ.js');
|
|
7
7
|
require('../chunk-SNAQBZPT.js');
|
|
8
8
|
var react = require('react');
|
|
9
9
|
var reactGoogleMaps = require('@vis.gl/react-google-maps');
|
|
@@ -141,7 +141,7 @@ async function fetchMapsConfig() {
|
|
|
141
141
|
const { apiUrl, apiKey } = getApiConfig();
|
|
142
142
|
if (apiKey) {
|
|
143
143
|
try {
|
|
144
|
-
const res = await
|
|
144
|
+
const res = await chunkC7J4XLBN_js.sonorFetch(
|
|
145
145
|
`${apiUrl}/api/public/maps/config`,
|
|
146
146
|
{ headers: { "Content-Type": "application/json" } },
|
|
147
147
|
{ apiKey }
|
|
@@ -181,7 +181,7 @@ async function fetchNearbyPlaces(params) {
|
|
|
181
181
|
});
|
|
182
182
|
if (params.radius) searchParams.set("radius", String(params.radius));
|
|
183
183
|
if (params.limit) searchParams.set("limit", String(params.limit));
|
|
184
|
-
const res = await
|
|
184
|
+
const res = await chunkC7J4XLBN_js.sonorFetch(
|
|
185
185
|
`${apiUrl}/api/public/maps/nearby-places?${searchParams}`,
|
|
186
186
|
{ headers: { "Content-Type": "application/json" } },
|
|
187
187
|
{ apiKey }
|
package/dist/maps/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { sonorFetch } from '../chunk-
|
|
3
|
-
import '../chunk-
|
|
4
|
-
import '../chunk-
|
|
2
|
+
import { sonorFetch } from '../chunk-5T3J24K4.mjs';
|
|
3
|
+
import '../chunk-YRKWJCML.mjs';
|
|
4
|
+
import '../chunk-K3PVUXKB.mjs';
|
|
5
5
|
import '../chunk-BEXTDHO4.mjs';
|
|
6
6
|
import { useState, useCallback, useEffect } from 'react';
|
|
7
7
|
import { APIProvider, Map as Map$1, AdvancedMarker, Pin, InfoWindow } from '@vis.gl/react-google-maps';
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sonordev/site-kit/mcp/transport — the rate-limited public MCP relay
|
|
3
|
+
*
|
|
4
|
+
* Netlify rate-limits a native function (declarative `config.rateLimit`), never
|
|
5
|
+
* a Next.js route handler. So a public MCP endpoint on Netlify takes this path:
|
|
6
|
+
*
|
|
7
|
+
* agent ─► /api/mcp netlify/functions/mcp.mjs, rate-limited
|
|
8
|
+
* │ createNetlifyMcpRelay: signs the request
|
|
9
|
+
* │ (HMAC-SHA256 of MCP_TRANSPORT_SECRET over a label)
|
|
10
|
+
* ▼
|
|
11
|
+
* https://<deploy-id>--<site>.netlify.app/api/mcp-internal
|
|
12
|
+
* │ createMcpInternalRoute: 403 unless signed,
|
|
13
|
+
* ▼ then dispatches to the /api/mcp handlers
|
|
14
|
+
* app/api/mcp/route.ts protectMcpHandlers(createMcpHandler(...))
|
|
15
|
+
*
|
|
16
|
+
* In production the function owns /api/mcp, and the Next route behind it
|
|
17
|
+
* refuses unsigned calls too, so no request reaches a tool without passing
|
|
18
|
+
* the rate limit. The relay targets the deploy permalink, so a request is
|
|
19
|
+
* answered by the same immutable deploy that received it, and the base URL
|
|
20
|
+
* comes from the platform (`context`), never from a client header.
|
|
21
|
+
*
|
|
22
|
+
* Single source of truth for the relay. It replaced per-site copies in
|
|
23
|
+
* upforge-nextjs and wirsch-law-nextjs (`lib/mcp/transport-auth.ts`, the
|
|
24
|
+
* `protect()` wrapper, the `/api/mcp-internal` relay and the function body);
|
|
25
|
+
* don't fork it back into a site.
|
|
26
|
+
*
|
|
27
|
+
* Node only (`crypto`), and deliberately not re-exported from the
|
|
28
|
+
* `@sonordev/site-kit/mcp` barrel so that barrel stays runtime-neutral.
|
|
29
|
+
* Nothing here imports `server-only`: the Netlify function runs in plain Node,
|
|
30
|
+
* where `server-only` throws on import.
|
|
31
|
+
*
|
|
32
|
+
* Setup: `MCP_TRANSPORT_SECRET` (`openssl rand -hex 32`) in every deploy
|
|
33
|
+
* context, Functions scope. See the module README for the three files.
|
|
34
|
+
*/
|
|
35
|
+
/** Env var holding the relay's HMAC key. */
|
|
36
|
+
export declare const MCP_TRANSPORT_SECRET_ENV = "MCP_TRANSPORT_SECRET";
|
|
37
|
+
/** Default request header carrying the signature (and the response marker). */
|
|
38
|
+
export declare const DEFAULT_MCP_TRANSPORT_HEADER = "x-site-mcp-transport";
|
|
39
|
+
/** Default HMAC message. Changing it invalidates every signature at once. */
|
|
40
|
+
export declare const DEFAULT_MCP_TRANSPORT_LABEL = "site-mcp-transport-v1";
|
|
41
|
+
/** Path of the signed Next route the function relays to. */
|
|
42
|
+
export declare const MCP_INTERNAL_PATH = "/api/mcp-internal";
|
|
43
|
+
/** Value of the marker header on every relayed response. Release checks assert it. */
|
|
44
|
+
export declare const MCP_RELAY_MARKER = "netlify-rate-limited-v1";
|
|
45
|
+
/** Largest POST body the relay forwards (tool arguments), in bytes. */
|
|
46
|
+
export declare const MCP_RELAY_MAX_BODY_BYTES: number;
|
|
47
|
+
/** Upstream timeout. Under Netlify's 60 s synchronous function limit, so the 502 is ours. */
|
|
48
|
+
export declare const MCP_RELAY_TIMEOUT_MS = 55000;
|
|
49
|
+
/** Methods the MCP wire uses. Anything else gets a 405. */
|
|
50
|
+
export declare const MCP_RELAY_METHODS: readonly ['GET', 'POST', 'OPTIONS', 'DELETE'];
|
|
51
|
+
export interface McpTransportOptions {
|
|
52
|
+
/**
|
|
53
|
+
* Request header that carries the signature. The relay also stamps it on
|
|
54
|
+
* responses with {@link MCP_RELAY_MARKER}. Must match between the function
|
|
55
|
+
* and the Next routes. Default {@link DEFAULT_MCP_TRANSPORT_HEADER}.
|
|
56
|
+
*/
|
|
57
|
+
header?: string;
|
|
58
|
+
/**
|
|
59
|
+
* HMAC message. Must match between the function and the Next routes.
|
|
60
|
+
* Default {@link DEFAULT_MCP_TRANSPORT_LABEL}.
|
|
61
|
+
*/
|
|
62
|
+
label?: string;
|
|
63
|
+
/**
|
|
64
|
+
* The HMAC key, or a getter for it. Default: `MCP_TRANSPORT_SECRET`, read on
|
|
65
|
+
* every request (`Netlify.env` when present, else `process.env`).
|
|
66
|
+
*/
|
|
67
|
+
secret?: string | (() => string | undefined);
|
|
68
|
+
}
|
|
69
|
+
export type McpRouteHandler = (request: Request) => Response | Promise<Response>;
|
|
70
|
+
/** The four method handlers an MCP route exports (what `createMcpHandler` returns). */
|
|
71
|
+
export interface McpRouteHandlerSet {
|
|
72
|
+
GET: McpRouteHandler;
|
|
73
|
+
POST: McpRouteHandler;
|
|
74
|
+
DELETE: McpRouteHandler;
|
|
75
|
+
OPTIONS: McpRouteHandler;
|
|
76
|
+
}
|
|
77
|
+
export interface ProtectMcpHandlersOptions extends McpTransportOptions {
|
|
78
|
+
/**
|
|
79
|
+
* Refuse unsigned requests. Default: `NODE_ENV === 'production'`, read per
|
|
80
|
+
* request, so `next dev` still answers a local MCP client directly.
|
|
81
|
+
*/
|
|
82
|
+
enforce?: boolean | (() => boolean);
|
|
83
|
+
}
|
|
84
|
+
export interface NetlifyMcpRelayOptions extends McpTransportOptions {
|
|
85
|
+
/** The signed Next route, on the same deploy. Default {@link MCP_INTERNAL_PATH}. */
|
|
86
|
+
internalPath?: string;
|
|
87
|
+
/** Error text for the 502 when the upstream fails or times out. */
|
|
88
|
+
timeoutMessage?: string;
|
|
89
|
+
}
|
|
90
|
+
/** The parts of Netlify's function `context` the relay reads. */
|
|
91
|
+
export interface NetlifyRelayContext {
|
|
92
|
+
deploy?: {
|
|
93
|
+
id?: string;
|
|
94
|
+
};
|
|
95
|
+
site?: {
|
|
96
|
+
name?: string;
|
|
97
|
+
url?: string;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/** The signature a relayed request carries: hex HMAC-SHA256 of `label` under `secret`. */
|
|
101
|
+
export declare function signMcpTransport(secret: string, label?: string): string;
|
|
102
|
+
/**
|
|
103
|
+
* True when the request came through the rate-limited relay: its transport
|
|
104
|
+
* header holds the HMAC of `label` under `MCP_TRANSPORT_SECRET`, compared in
|
|
105
|
+
* constant time. False when the secret is unset.
|
|
106
|
+
*/
|
|
107
|
+
export declare function hasMcpTransportAuthorization(request: Request, options?: McpTransportOptions): boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Wrap the `/api/mcp` handlers so, in production, they answer only relayed
|
|
110
|
+
* (signed) requests. The public path is the function's; this closes the Next
|
|
111
|
+
* handler behind it so nothing can skip the rate limit.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* // app/api/mcp/route.ts
|
|
116
|
+
* export const { POST, GET, DELETE, OPTIONS } = protectMcpHandlers(
|
|
117
|
+
* createMcpHandler({ server: mcpServer, baseUrl: SITE_URL, allowedOrigins: '*' }),
|
|
118
|
+
* )
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
export declare function protectMcpHandlers(handlers: McpRouteHandlerSet, options?: ProtectMcpHandlersOptions): McpRouteHandlerSet;
|
|
122
|
+
/**
|
|
123
|
+
* Handlers for `/api/mcp-internal`, the relay's target. 403 without a valid
|
|
124
|
+
* signature in every environment, then dispatch by method to the `/api/mcp`
|
|
125
|
+
* handlers.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* // app/api/mcp-internal/route.ts
|
|
130
|
+
* import * as mcp from '../mcp/route'
|
|
131
|
+
* export const { POST, GET, DELETE, OPTIONS } = createMcpInternalRoute(mcp)
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
export declare function createMcpInternalRoute(handlers: McpRouteHandlerSet, options?: McpTransportOptions): McpRouteHandlerSet;
|
|
135
|
+
/**
|
|
136
|
+
* The default export for `netlify/functions/mcp.mjs`. Signs the request and
|
|
137
|
+
* relays it to {@link MCP_INTERNAL_PATH} on the same deploy.
|
|
138
|
+
*
|
|
139
|
+
* - 503 when `MCP_TRANSPORT_SECRET` is unset; 405 for methods MCP doesn't use.
|
|
140
|
+
* - Strips `host` and `content-length`, overwrites any client-sent transport
|
|
141
|
+
* header with the real signature.
|
|
142
|
+
* - POST bodies over 64 KB get a 413 and are never forwarded.
|
|
143
|
+
* - `redirect: 'error'` and a 55 s timeout; any upstream failure is a 502.
|
|
144
|
+
* - Responses drop the encoding/length headers fetch already undid, and gain
|
|
145
|
+
* `Cache-Control: no-store` plus the transport header set to
|
|
146
|
+
* {@link MCP_RELAY_MARKER}.
|
|
147
|
+
*
|
|
148
|
+
* The function file must still declare its own literal `config`: Netlify
|
|
149
|
+
* reads `path` and `rateLimit` statically from that file, so they can't come
|
|
150
|
+
* from an import.
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```js
|
|
154
|
+
* // netlify/functions/mcp.mjs
|
|
155
|
+
* import { createNetlifyMcpRelay } from '@sonordev/site-kit/mcp/transport'
|
|
156
|
+
*
|
|
157
|
+
* export default createNetlifyMcpRelay()
|
|
158
|
+
*
|
|
159
|
+
* export const config = {
|
|
160
|
+
* path: '/api/mcp',
|
|
161
|
+
* rateLimit: { windowSize: 60, windowLimit: 60, aggregateBy: ['ip', 'domain'] },
|
|
162
|
+
* }
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
export declare function createNetlifyMcpRelay(options?: NetlifyMcpRelayOptions): (request: Request, context: NetlifyRelayContext) => Promise<Response>;
|
|
166
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/mcp/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AASH,4CAA4C;AAC5C,eAAO,MAAM,wBAAwB,yBAAyB,CAAA;AAE9D,+EAA+E;AAC/E,eAAO,MAAM,4BAA4B,yBAAyB,CAAA;AAElE,6EAA6E;AAC7E,eAAO,MAAM,2BAA2B,0BAA0B,CAAA;AAElE,4DAA4D;AAC5D,eAAO,MAAM,iBAAiB,sBAAsB,CAAA;AAEpD,sFAAsF;AACtF,eAAO,MAAM,gBAAgB,4BAA4B,CAAA;AAEzD,uEAAuE;AACvE,eAAO,MAAM,wBAAwB,QAAY,CAAA;AAEjD,6FAA6F;AAC7F,eAAO,MAAM,oBAAoB,QAAS,CAAA;AAE1C,2DAA2D;AAC3D,eAAO,MAAM,iBAAiB,YAAI,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAU,CAAA;AAM9E,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,SAAS,CAAC,CAAA;CAC7C;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;AAEhF,uFAAuF;AACvF,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,eAAe,CAAA;IACpB,IAAI,EAAE,eAAe,CAAA;IACrB,MAAM,EAAE,eAAe,CAAA;IACvB,OAAO,EAAE,eAAe,CAAA;CACzB;AAED,MAAM,WAAW,yBAA0B,SAAQ,mBAAmB;IACpE;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,CAAA;CACpC;AAED,MAAM,WAAW,sBAAuB,SAAQ,mBAAmB;IACjE,oFAAoF;IACpF,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,mEAAmE;IACnE,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,iEAAiE;AACjE,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACxB,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CACvC;AA6BD,0FAA0F;AAC1F,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,GAAE,MAAoC,GAAG,MAAM,CAEpG;AAUD;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAEzG;AAYD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,GAAE,yBAA8B,GACtC,kBAAkB,CAgBpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,GAAE,mBAAwB,GAChC,kBAAkB,CAkBpB;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,sBAA2B,GACnC,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,QAAQ,CAAC,CAmDvE"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkYMZNEDFP_js = require('../chunk-YMZNEDFP.js');
|
|
4
|
+
require('../chunk-SNAQBZPT.js');
|
|
5
|
+
var crypto = require('crypto');
|
|
6
|
+
|
|
7
|
+
var MCP_TRANSPORT_SECRET_ENV = "MCP_TRANSPORT_SECRET";
|
|
8
|
+
var DEFAULT_MCP_TRANSPORT_HEADER = "x-site-mcp-transport";
|
|
9
|
+
var DEFAULT_MCP_TRANSPORT_LABEL = "site-mcp-transport-v1";
|
|
10
|
+
var MCP_INTERNAL_PATH = "/api/mcp-internal";
|
|
11
|
+
var MCP_RELAY_MARKER = "netlify-rate-limited-v1";
|
|
12
|
+
var MCP_RELAY_MAX_BODY_BYTES = 64 * 1024;
|
|
13
|
+
var MCP_RELAY_TIMEOUT_MS = 55e3;
|
|
14
|
+
var MCP_RELAY_METHODS = ["GET", "POST", "OPTIONS", "DELETE"];
|
|
15
|
+
function resolveTransport(options = {}) {
|
|
16
|
+
const header = (options.header ?? DEFAULT_MCP_TRANSPORT_HEADER).toLowerCase();
|
|
17
|
+
const label = options.label ?? DEFAULT_MCP_TRANSPORT_LABEL;
|
|
18
|
+
if (!/^[a-z0-9-]+$/.test(header)) {
|
|
19
|
+
throw new TypeError(`@sonordev/site-kit/mcp/transport: invalid header name ${JSON.stringify(options.header)}`);
|
|
20
|
+
}
|
|
21
|
+
if (!label) throw new TypeError("@sonordev/site-kit/mcp/transport: label must not be empty");
|
|
22
|
+
return { header, label, secret: options.secret };
|
|
23
|
+
}
|
|
24
|
+
function readSecret(secret) {
|
|
25
|
+
if (typeof secret === "function") return secret();
|
|
26
|
+
if (secret !== void 0) return secret;
|
|
27
|
+
const netlify = globalThis.Netlify;
|
|
28
|
+
return netlify?.env?.get(MCP_TRANSPORT_SECRET_ENV) || process.env[MCP_TRANSPORT_SECRET_ENV];
|
|
29
|
+
}
|
|
30
|
+
function signMcpTransport(secret, label = DEFAULT_MCP_TRANSPORT_LABEL) {
|
|
31
|
+
return crypto.createHmac("sha256", secret).update(label).digest("hex");
|
|
32
|
+
}
|
|
33
|
+
function verify(request, transport) {
|
|
34
|
+
const key = readSecret(transport.secret);
|
|
35
|
+
const supplied = request.headers.get(transport.header) || "";
|
|
36
|
+
if (!key || !/^[a-f0-9]{64}$/.test(supplied)) return false;
|
|
37
|
+
const expected = crypto.createHmac("sha256", key).update(transport.label).digest();
|
|
38
|
+
return crypto.timingSafeEqual(expected, Buffer.from(supplied, "hex"));
|
|
39
|
+
}
|
|
40
|
+
function hasMcpTransportAuthorization(request, options = {}) {
|
|
41
|
+
return verify(request, resolveTransport(options));
|
|
42
|
+
}
|
|
43
|
+
function isEnforced(enforce) {
|
|
44
|
+
if (typeof enforce === "function") return enforce();
|
|
45
|
+
if (enforce !== void 0) return enforce;
|
|
46
|
+
return process.env.NODE_ENV === "production";
|
|
47
|
+
}
|
|
48
|
+
function protectMcpHandlers(handlers, options = {}) {
|
|
49
|
+
const transport = resolveTransport(options);
|
|
50
|
+
const protect = (handler) => (request) => {
|
|
51
|
+
if (isEnforced(options.enforce) && !verify(request, transport)) {
|
|
52
|
+
return Response.json({ error: "Use the public MCP endpoint" }, { status: 403 });
|
|
53
|
+
}
|
|
54
|
+
return handler(request);
|
|
55
|
+
};
|
|
56
|
+
return {
|
|
57
|
+
GET: protect(handlers.GET),
|
|
58
|
+
POST: protect(handlers.POST),
|
|
59
|
+
DELETE: protect(handlers.DELETE),
|
|
60
|
+
OPTIONS: protect(handlers.OPTIONS)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function createMcpInternalRoute(handlers, options = {}) {
|
|
64
|
+
const transport = resolveTransport(options);
|
|
65
|
+
const relay = (request) => {
|
|
66
|
+
if (!verify(request, transport)) return Response.json({ error: "Forbidden" }, { status: 403 });
|
|
67
|
+
switch (request.method) {
|
|
68
|
+
case "GET":
|
|
69
|
+
return handlers.GET(request);
|
|
70
|
+
case "POST":
|
|
71
|
+
return handlers.POST(request);
|
|
72
|
+
case "DELETE":
|
|
73
|
+
return handlers.DELETE(request);
|
|
74
|
+
case "OPTIONS":
|
|
75
|
+
return handlers.OPTIONS(request);
|
|
76
|
+
default:
|
|
77
|
+
return new Response(null, { status: 405 });
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
return { GET: relay, POST: relay, DELETE: relay, OPTIONS: relay };
|
|
81
|
+
}
|
|
82
|
+
function createNetlifyMcpRelay(options = {}) {
|
|
83
|
+
const transport = resolveTransport(options);
|
|
84
|
+
const internalPath = options.internalPath ?? MCP_INTERNAL_PATH;
|
|
85
|
+
const timeoutMessage = options.timeoutMessage ?? "MCP request timed out. Try again.";
|
|
86
|
+
const unavailable = () => Response.json({ error: "MCP is temporarily unavailable" }, { status: 503 });
|
|
87
|
+
return async function relayMcp(request, context) {
|
|
88
|
+
const key = readSecret(transport.secret);
|
|
89
|
+
if (!key) return unavailable();
|
|
90
|
+
if (!MCP_RELAY_METHODS.includes(request.method)) return new Response(null, { status: 405 });
|
|
91
|
+
const base = context?.deploy?.id && context.site?.name ? `https://${context.deploy.id}--${context.site.name}.netlify.app` : context?.site?.url;
|
|
92
|
+
if (!base) return unavailable();
|
|
93
|
+
const target = new URL(internalPath, base);
|
|
94
|
+
const headers = new Headers(request.headers);
|
|
95
|
+
headers.delete("host");
|
|
96
|
+
headers.delete("content-length");
|
|
97
|
+
headers.set(transport.header, signMcpTransport(key, transport.label));
|
|
98
|
+
let body;
|
|
99
|
+
if (request.method === "POST") {
|
|
100
|
+
const bytes = await chunkYMZNEDFP_js.readCappedBody(request, MCP_RELAY_MAX_BODY_BYTES);
|
|
101
|
+
if (!bytes) return Response.json({ error: "Tool arguments are too large" }, { status: 413 });
|
|
102
|
+
body = bytes;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const response = await fetch(target, {
|
|
106
|
+
method: request.method,
|
|
107
|
+
headers,
|
|
108
|
+
body,
|
|
109
|
+
redirect: "error",
|
|
110
|
+
signal: AbortSignal.timeout(MCP_RELAY_TIMEOUT_MS)
|
|
111
|
+
});
|
|
112
|
+
const outgoing = new Headers(response.headers);
|
|
113
|
+
for (const name of ["content-encoding", "content-length", "transfer-encoding"]) outgoing.delete(name);
|
|
114
|
+
outgoing.set("Cache-Control", "no-store");
|
|
115
|
+
outgoing.set(transport.header, MCP_RELAY_MARKER);
|
|
116
|
+
return new Response(response.body, { status: response.status, headers: outgoing });
|
|
117
|
+
} catch {
|
|
118
|
+
return Response.json({ error: timeoutMessage }, { status: 502 });
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
exports.DEFAULT_MCP_TRANSPORT_HEADER = DEFAULT_MCP_TRANSPORT_HEADER;
|
|
124
|
+
exports.DEFAULT_MCP_TRANSPORT_LABEL = DEFAULT_MCP_TRANSPORT_LABEL;
|
|
125
|
+
exports.MCP_INTERNAL_PATH = MCP_INTERNAL_PATH;
|
|
126
|
+
exports.MCP_RELAY_MARKER = MCP_RELAY_MARKER;
|
|
127
|
+
exports.MCP_RELAY_MAX_BODY_BYTES = MCP_RELAY_MAX_BODY_BYTES;
|
|
128
|
+
exports.MCP_RELAY_METHODS = MCP_RELAY_METHODS;
|
|
129
|
+
exports.MCP_RELAY_TIMEOUT_MS = MCP_RELAY_TIMEOUT_MS;
|
|
130
|
+
exports.MCP_TRANSPORT_SECRET_ENV = MCP_TRANSPORT_SECRET_ENV;
|
|
131
|
+
exports.createMcpInternalRoute = createMcpInternalRoute;
|
|
132
|
+
exports.createNetlifyMcpRelay = createNetlifyMcpRelay;
|
|
133
|
+
exports.hasMcpTransportAuthorization = hasMcpTransportAuthorization;
|
|
134
|
+
exports.protectMcpHandlers = protectMcpHandlers;
|
|
135
|
+
exports.signMcpTransport = signMcpTransport;
|
|
136
|
+
//# sourceMappingURL=transport.js.map
|
|
137
|
+
//# sourceMappingURL=transport.js.map
|