@sonordev/site-kit 6.3.6 → 6.5.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-44PDLORZ.js +34 -0
- package/dist/{AnalyticsProvider-L3GWK5PO.js.map → AnalyticsProvider-44PDLORZ.js.map} +1 -1
- package/dist/AnalyticsProvider-F6KHEAOX.mjs +13 -0
- package/dist/{AnalyticsProvider-XDVJTAPN.mjs.map → AnalyticsProvider-F6KHEAOX.mjs.map} +1 -1
- package/dist/{ArticleViewTracker-YMGW6ZXB.js → ArticleViewTracker-MGNVIHZ4.js} +6 -6
- package/dist/{ArticleViewTracker-YMGW6ZXB.js.map → ArticleViewTracker-MGNVIHZ4.js.map} +1 -1
- package/dist/{ArticleViewTracker-7AJJTGFY.mjs → ArticleViewTracker-NI4UNSY7.mjs} +5 -5
- package/dist/{ArticleViewTracker-7AJJTGFY.mjs.map → ArticleViewTracker-NI4UNSY7.mjs.map} +1 -1
- package/dist/{ChatWidget-MR64ZLOG.mjs → ChatWidget-XSALHDDW.mjs} +7 -7
- package/dist/{ChatWidget-MR64ZLOG.mjs.map → ChatWidget-XSALHDDW.mjs.map} +1 -1
- package/dist/{ChatWidget-IAYEXLNX.js → ChatWidget-YBXJRR2Z.js} +10 -10
- package/dist/{ChatWidget-IAYEXLNX.js.map → ChatWidget-YBXJRR2Z.js.map} +1 -1
- package/dist/EngageWidget-DUEXPVOY.mjs +13 -0
- package/dist/{EngageWidget-RANSIKC3.mjs.map → EngageWidget-DUEXPVOY.mjs.map} +1 -1
- package/dist/EngageWidget-MDCRS3BN.js +22 -0
- package/dist/{EngageWidget-BF7DH4GO.js.map → EngageWidget-MDCRS3BN.js.map} +1 -1
- package/dist/{FileField-VRKATE4G.js → FileField-AXU6KBSV.js} +6 -6
- package/dist/{FileField-VRKATE4G.js.map → FileField-AXU6KBSV.js.map} +1 -1
- package/dist/{FileField-7ZNQ5BID.mjs → FileField-UTLGR5ZN.mjs} +5 -5
- package/dist/{FileField-7ZNQ5BID.mjs.map → FileField-UTLGR5ZN.mjs.map} +1 -1
- package/dist/{FormSpotlight-QNH7XGYS.mjs → FormSpotlight-NWLCZMOT.mjs} +3 -3
- package/dist/{FormSpotlight-QNH7XGYS.mjs.map → FormSpotlight-NWLCZMOT.mjs.map} +1 -1
- package/dist/{FormSpotlight-VQJ2QWIU.js → FormSpotlight-RR5KJDIH.js} +9 -9
- package/dist/{FormSpotlight-VQJ2QWIU.js.map → FormSpotlight-RR5KJDIH.js.map} +1 -1
- package/dist/{FormStage-3JIN6X6O.js → FormStage-GFNX4GLU.js} +9 -9
- package/dist/{FormStage-3JIN6X6O.js.map → FormStage-GFNX4GLU.js.map} +1 -1
- package/dist/{FormStage-XB4JNFCH.mjs → FormStage-V4XKLI6J.mjs} +3 -3
- package/dist/{FormStage-XB4JNFCH.mjs.map → FormStage-V4XKLI6J.mjs.map} +1 -1
- package/dist/ManagedForm-4AD3LWSC.js +25 -0
- package/dist/{ManagedForm-Q5UFMUBE.js.map → ManagedForm-4AD3LWSC.js.map} +1 -1
- package/dist/ManagedForm-4IWPG6AZ.mjs +16 -0
- package/dist/{ManagedForm-E6F4FLVY.mjs.map → ManagedForm-4IWPG6AZ.mjs.map} +1 -1
- package/dist/{ManagedNewsletterForm-OEEC3GVY.mjs → ManagedNewsletterForm-ARSY2VHC.mjs} +6 -6
- package/dist/{ManagedNewsletterForm-OEEC3GVY.mjs.map → ManagedNewsletterForm-ARSY2VHC.mjs.map} +1 -1
- package/dist/{ManagedNewsletterForm-UTNOJ5T4.js → ManagedNewsletterForm-HJZNUABY.js} +7 -7
- package/dist/{ManagedNewsletterForm-UTNOJ5T4.js.map → ManagedNewsletterForm-HJZNUABY.js.map} +1 -1
- package/dist/{SignalCore-PWN5X6FF.mjs → SignalCore-N2LKG4XN.mjs} +5 -5
- package/dist/{SignalCore-PWN5X6FF.mjs.map → SignalCore-N2LKG4XN.mjs.map} +1 -1
- package/dist/{SignalCore-4AUWHMYK.js → SignalCore-P2JUY2BO.js} +11 -11
- package/dist/{SignalCore-4AUWHMYK.js.map → SignalCore-P2JUY2BO.js.map} +1 -1
- package/dist/SitemapSync-KFY4PJ7G.js +19 -0
- package/dist/{SitemapSync-DHJ25PSH.js.map → SitemapSync-KFY4PJ7G.js.map} +1 -1
- package/dist/SitemapSync-UYDL7T4M.mjs +10 -0
- package/dist/{SitemapSync-ZA2MNM63.mjs.map → SitemapSync-UYDL7T4M.mjs.map} +1 -1
- package/dist/analytics/index.js +14 -13
- package/dist/analytics/index.mjs +6 -5
- 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 +23 -0
- package/dist/articles/types.d.ts.map +1 -1
- package/dist/{chunk-UAOQVHZW.js → chunk-34XL7TVH.js} +4 -4
- package/dist/{chunk-UAOQVHZW.js.map → chunk-34XL7TVH.js.map} +1 -1
- package/dist/chunk-3AOKYAZ2.js +135 -0
- package/dist/chunk-3AOKYAZ2.js.map +1 -0
- package/dist/{chunk-BJKA3XRS.mjs → chunk-3BLKKF67.mjs} +4 -4
- package/dist/{chunk-BJKA3XRS.mjs.map → chunk-3BLKKF67.mjs.map} +1 -1
- package/dist/{chunk-3J5L5DIK.js → chunk-3EAS4PAT.js} +3 -3
- package/dist/{chunk-3J5L5DIK.js.map → chunk-3EAS4PAT.js.map} +1 -1
- package/dist/{chunk-SB4CJX2X.mjs → chunk-42LLFAJQ.mjs} +3 -3
- package/dist/{chunk-SB4CJX2X.mjs.map → chunk-42LLFAJQ.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-VGBUC2JP.js → chunk-5ZMTEK7U.js} +10 -10
- package/dist/{chunk-VGBUC2JP.js.map → chunk-5ZMTEK7U.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-E7EV2AHT.js → chunk-6OQYRVMI.js} +9 -9
- package/dist/{chunk-E7EV2AHT.js.map → chunk-6OQYRVMI.js.map} +1 -1
- package/dist/{chunk-UJIKP35G.mjs → chunk-7F66Y4MA.mjs} +4 -4
- package/dist/{chunk-UJIKP35G.mjs.map → chunk-7F66Y4MA.mjs.map} +1 -1
- package/dist/{chunk-6MOGGEY3.mjs → chunk-A2HL22BO.mjs} +3 -3
- package/dist/{chunk-6MOGGEY3.mjs.map → chunk-A2HL22BO.mjs.map} +1 -1
- package/dist/{chunk-7NFSSPGM.js → chunk-AL4Z6VYF.js} +8 -8
- package/dist/{chunk-7NFSSPGM.js.map → chunk-AL4Z6VYF.js.map} +1 -1
- package/dist/{chunk-W46QIVBM.mjs → chunk-B6QPCAEG.mjs} +3 -3
- package/dist/{chunk-W46QIVBM.mjs.map → chunk-B6QPCAEG.mjs.map} +1 -1
- package/dist/{chunk-EB33U2WT.js → chunk-BHZHWXER.js} +131 -71
- package/dist/chunk-BHZHWXER.js.map +1 -0
- package/dist/{chunk-R26FAQWF.mjs → chunk-BNKBRLJK.mjs} +3 -3
- package/dist/{chunk-R26FAQWF.mjs.map → chunk-BNKBRLJK.mjs.map} +1 -1
- package/dist/{chunk-A6UYV475.mjs → chunk-CABEVWKJ.mjs} +4 -24
- package/dist/chunk-CABEVWKJ.mjs.map +1 -0
- 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-SAOUAIRS.js → chunk-CM4AVKXR.js} +14 -14
- package/dist/{chunk-SAOUAIRS.js.map → chunk-CM4AVKXR.js.map} +1 -1
- package/dist/{chunk-KMONA7CT.mjs → chunk-D3WJMSTB.mjs} +3 -3
- package/dist/{chunk-KMONA7CT.mjs.map → chunk-D3WJMSTB.mjs.map} +1 -1
- package/dist/{chunk-3Q3Y6OQE.mjs → chunk-DWZGW77V.mjs} +4 -4
- package/dist/{chunk-3Q3Y6OQE.mjs.map → chunk-DWZGW77V.mjs.map} +1 -1
- package/dist/{chunk-L6BQIDSZ.mjs → chunk-FPVYKSXE.mjs} +13 -10
- package/dist/chunk-FPVYKSXE.mjs.map +1 -0
- package/dist/{chunk-5XDOBM5S.js → chunk-G2KGHIAP.js} +3 -3
- package/dist/{chunk-5XDOBM5S.js.map → chunk-G2KGHIAP.js.map} +1 -1
- package/dist/chunk-GFSQMK35.mjs +127 -0
- package/dist/chunk-GFSQMK35.mjs.map +1 -0
- package/dist/chunk-GIGUDKLS.mjs +6 -0
- package/dist/{chunk-3V6DIQI7.mjs.map → chunk-GIGUDKLS.mjs.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-J4C3JAJF.js → chunk-IO65ROAO.js} +15 -15
- package/dist/{chunk-J4C3JAJF.js.map → chunk-IO65ROAO.js.map} +1 -1
- package/dist/chunk-JCDX37BY.js +58 -0
- package/dist/chunk-JCDX37BY.js.map +1 -0
- package/dist/{chunk-5J4FKRGH.mjs → chunk-MDQLON5R.mjs} +5 -5
- package/dist/{chunk-5J4FKRGH.mjs.map → chunk-MDQLON5R.mjs.map} +1 -1
- package/dist/{chunk-3POB4JKR.js → chunk-MHKD34B4.js} +15 -15
- package/dist/{chunk-3POB4JKR.js.map → chunk-MHKD34B4.js.map} +1 -1
- package/dist/chunk-MXJOKOFK.mjs +51 -0
- package/dist/chunk-MXJOKOFK.mjs.map +1 -0
- package/dist/{chunk-5RNAUYWE.js → chunk-NH2P27MA.js} +4 -27
- package/dist/chunk-NH2P27MA.js.map +1 -0
- package/dist/{chunk-5DZVQW35.mjs → chunk-OFBEBMXN.mjs} +4 -4
- package/dist/{chunk-5DZVQW35.mjs.map → chunk-OFBEBMXN.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-2UZULHI4.js → chunk-OY3PDIB3.js} +10 -10
- package/dist/{chunk-2UZULHI4.js.map → chunk-OY3PDIB3.js.map} +1 -1
- package/dist/{chunk-SNXJXPB6.js → chunk-PFDRQJKJ.js} +13 -13
- package/dist/{chunk-SNXJXPB6.js.map → chunk-PFDRQJKJ.js.map} +1 -1
- package/dist/{chunk-CXYGKK4U.mjs → chunk-PGYOBEPL.mjs} +3 -3
- package/dist/{chunk-CXYGKK4U.mjs.map → chunk-PGYOBEPL.mjs.map} +1 -1
- package/dist/{chunk-TE3YNZVH.js → chunk-PIPGH7GA.js} +4 -4
- package/dist/{chunk-TE3YNZVH.js.map → chunk-PIPGH7GA.js.map} +1 -1
- package/dist/chunk-PURI56RE.js +8 -0
- package/dist/{chunk-6SBRD5KT.js.map → chunk-PURI56RE.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-PMWF5CRL.js → chunk-QCS7NWMA.js} +15 -12
- package/dist/chunk-QCS7NWMA.js.map +1 -0
- 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-ANYCUTS5.js → chunk-RMDH2HMW.js} +3 -3
- package/dist/{chunk-ANYCUTS5.js.map → chunk-RMDH2HMW.js.map} +1 -1
- package/dist/{chunk-NAA5J2JM.js → chunk-RQROZLH3.js} +47 -51
- package/dist/chunk-RQROZLH3.js.map +1 -0
- package/dist/{chunk-X5J3PYZJ.js → chunk-SUGBIEYQ.js} +9 -9
- package/dist/{chunk-X5J3PYZJ.js.map → chunk-SUGBIEYQ.js.map} +1 -1
- package/dist/{chunk-LFF32POH.mjs → chunk-SVCKJ5KH.mjs} +4 -4
- package/dist/{chunk-LFF32POH.mjs.map → chunk-SVCKJ5KH.mjs.map} +1 -1
- package/dist/{chunk-YKZ3EVAQ.mjs → chunk-THO4NS6Z.mjs} +3 -3
- package/dist/{chunk-YKZ3EVAQ.mjs.map → chunk-THO4NS6Z.mjs.map} +1 -1
- package/dist/{chunk-K6ZQVDP4.mjs → chunk-TPEPAHU5.mjs} +3 -3
- package/dist/{chunk-K6ZQVDP4.mjs.map → chunk-TPEPAHU5.mjs.map} +1 -1
- package/dist/{chunk-FRUBUQOH.js → chunk-U577ITSA.js} +5 -5
- package/dist/{chunk-FRUBUQOH.js.map → chunk-U577ITSA.js.map} +1 -1
- package/dist/{chunk-B2NO5QVY.mjs → chunk-UCQDE5UH.mjs} +3 -3
- package/dist/{chunk-B2NO5QVY.mjs.map → chunk-UCQDE5UH.mjs.map} +1 -1
- package/dist/{chunk-KB7SIIVY.mjs → chunk-VA2O5OSV.mjs} +3 -3
- package/dist/{chunk-KB7SIIVY.mjs.map → chunk-VA2O5OSV.mjs.map} +1 -1
- package/dist/{chunk-6I5O5CTL.mjs → chunk-VJTKIKZY.mjs} +128 -68
- package/dist/chunk-VJTKIKZY.mjs.map +1 -0
- package/dist/{chunk-OLVCGOXZ.js → chunk-VUAGR6VL.js} +12 -12
- package/dist/{chunk-OLVCGOXZ.js.map → chunk-VUAGR6VL.js.map} +1 -1
- package/dist/{chunk-PQICWWQW.mjs → chunk-XEOIGCUE.mjs} +5 -5
- package/dist/{chunk-PQICWWQW.mjs.map → chunk-XEOIGCUE.mjs.map} +1 -1
- package/dist/chunk-YMZNEDFP.js +39 -0
- package/dist/chunk-YMZNEDFP.js.map +1 -0
- package/dist/chunk-Z2URIOKG.mjs +36 -0
- package/dist/chunk-Z2URIOKG.mjs.map +1 -0
- package/dist/{chunk-2MEMK5XC.js → chunk-ZXC56JS3.js} +14 -14
- package/dist/{chunk-2MEMK5XC.js.map → chunk-ZXC56JS3.js.map} +1 -1
- package/dist/cli/index.js +3 -3
- package/dist/cli/index.mjs +1 -1
- package/dist/client/index.js +4 -4
- package/dist/client/index.mjs +3 -3
- package/dist/commerce/index.js +59 -58
- package/dist/commerce/index.mjs +7 -6
- 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 +11 -10
- package/dist/engage/index.js.map +1 -1
- package/dist/engage/index.mjs +8 -7
- package/dist/engage/index.mjs.map +1 -1
- package/dist/fleet/index.js +10 -9
- package/dist/fleet/index.mjs +6 -5
- 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 +65 -64
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +11 -10
- package/dist/index.mjs.map +1 -1
- package/dist/layout/SiteKitClientProviders.d.ts.map +1 -1
- package/dist/layout/client.d.ts.map +1 -1
- package/dist/layout/client.js +10 -9
- package/dist/layout/client.mjs +9 -8
- package/dist/layout/index.js +12 -11
- package/dist/layout/index.js.map +1 -1
- package/dist/layout/index.mjs +10 -9
- package/dist/layout/index.mjs.map +1 -1
- package/dist/layout/types.d.ts +6 -0
- package/dist/layout/types.d.ts.map +1 -1
- 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/portfolio/contract.d.ts +93 -0
- package/dist/portfolio/contract.d.ts.map +1 -1
- package/dist/portfolio/contract.js +94 -1
- package/dist/portfolio/contract.js.map +1 -1
- package/dist/portfolio/contract.mjs +79 -2
- package/dist/portfolio/contract.mjs.map +1 -1
- package/dist/search/index.js +4 -4
- package/dist/search/index.mjs +3 -3
- package/dist/seo/client.js +7 -6
- package/dist/seo/client.mjs +6 -5
- package/dist/seo/index.js +7 -6
- package/dist/seo/index.js.map +1 -1
- package/dist/seo/index.mjs +6 -5
- package/dist/seo/index.mjs.map +1 -1
- package/dist/seo/register-sitemap-cli.js +1 -1
- package/dist/seo/register-sitemap-cli.mjs +1 -1
- 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/frame.d.ts +15 -0
- package/dist/shared/frame.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/{writeLLMsTxt-LYYC3ZSN.mjs → writeLLMsTxt-IYJILIU3.mjs} +3 -3
- package/dist/{writeLLMsTxt-XHLHJ5XY.js → writeLLMsTxt-SQG4B63X.js} +4 -4
- package/package.json +24 -21
- package/dist/AnalyticsProvider-L3GWK5PO.js +0 -33
- package/dist/AnalyticsProvider-XDVJTAPN.mjs +0 -12
- 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-5RNAUYWE.js.map +0 -1
- 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-A6UYV475.mjs.map +0 -1
- package/dist/chunk-EB33U2WT.js.map +0 -1
- package/dist/chunk-L6BQIDSZ.mjs.map +0 -1
- package/dist/chunk-NAA5J2JM.js.map +0 -1
- package/dist/chunk-PMWF5CRL.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/llms/index.js
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
var chunkWS7MH3GD_js = require('../chunk-WS7MH3GD.js');
|
|
4
4
|
var chunk6FNFM7JL_js = require('../chunk-6FNFM7JL.js');
|
|
5
5
|
var chunkTBXWJJGT_js = require('../chunk-TBXWJJGT.js');
|
|
6
|
-
var
|
|
6
|
+
var chunkYMZNEDFP_js = require('../chunk-YMZNEDFP.js');
|
|
7
|
+
var chunkOIPG3EGM_js = require('../chunk-OIPG3EGM.js');
|
|
7
8
|
require('../chunk-KR7ATPAN.js');
|
|
8
|
-
require('../chunk-
|
|
9
|
+
require('../chunk-55J2KE7D.js');
|
|
9
10
|
require('../chunk-LVGSOW2F.js');
|
|
10
11
|
var chunkH6AU3WKF_js = require('../chunk-H6AU3WKF.js');
|
|
11
12
|
require('../chunk-J7QI3GDX.js');
|
|
@@ -47,7 +48,7 @@ function createLLMsTxtHandler(options = {}) {
|
|
|
47
48
|
});
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
|
-
const { markdown, metadata } = await
|
|
51
|
+
const { markdown, metadata } = await chunkOIPG3EGM_js.generateLLMsTxt(generateOptions);
|
|
51
52
|
return new Response(markdown, {
|
|
52
53
|
status: 200,
|
|
53
54
|
headers: llmsResponseHeaders(markdown, {
|
|
@@ -83,7 +84,7 @@ function createLLMsFullTxtHandler(options = {}) {
|
|
|
83
84
|
});
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
|
-
const { markdown, metadata } = await
|
|
87
|
+
const { markdown, metadata } = await chunkOIPG3EGM_js.generateLLMsFullTxt(generateOptions);
|
|
87
88
|
return new Response(markdown, {
|
|
88
89
|
status: 200,
|
|
89
90
|
headers: llmsResponseHeaders(markdown, {
|
|
@@ -138,7 +139,7 @@ function createLlmsRevalidateHandler(secret, options) {
|
|
|
138
139
|
const url = new URL(request.url);
|
|
139
140
|
const tokenFromQuery = url.searchParams.get("secret");
|
|
140
141
|
const token = authHeader?.replace(/^Bearer\s+/i, "") || tokenFromQuery;
|
|
141
|
-
if (!
|
|
142
|
+
if (!chunkYMZNEDFP_js.secretsMatch(token, secret)) {
|
|
142
143
|
return new Response(JSON.stringify({ error: "Invalid secret" }), {
|
|
143
144
|
status: 401,
|
|
144
145
|
headers: { "Content-Type": "application/json" }
|
|
@@ -172,6 +173,115 @@ function createLlmsRevalidateHandler(secret, options) {
|
|
|
172
173
|
};
|
|
173
174
|
}
|
|
174
175
|
|
|
176
|
+
// src/llms/seo-revalidate.ts
|
|
177
|
+
var SEO_REVALIDATION_MAX_BODY_BYTES = 16 * 1024;
|
|
178
|
+
var SEO_REVALIDATION_MAX_ITEMS = 100;
|
|
179
|
+
var LEGACY_PUBLICATION_PATH = "/blog";
|
|
180
|
+
function noStoreJson(body, status = 200) {
|
|
181
|
+
return Response.json(body, { status, headers: { "Cache-Control": "no-store" } });
|
|
182
|
+
}
|
|
183
|
+
function isLocalPath(value) {
|
|
184
|
+
if (typeof value !== "string" || value.length > 1024) return false;
|
|
185
|
+
try {
|
|
186
|
+
const path = decodeURIComponent(value);
|
|
187
|
+
return path.startsWith("/") && !path.startsWith("//") && !/[\\\s?#[\]:\x00-\x1f]/.test(path) && !path.split("/").some((part) => part === "." || part === "..");
|
|
188
|
+
} catch {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function isCacheTag(value) {
|
|
193
|
+
return typeof value === "string" && value.length > 0 && value.length <= 256 && !/[\x00-\x1f]/.test(value);
|
|
194
|
+
}
|
|
195
|
+
function parseSeoRevalidationPayload(value) {
|
|
196
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid payload");
|
|
197
|
+
const body = value;
|
|
198
|
+
if (body.revalidateAll !== void 0 && typeof body.revalidateAll !== "boolean") {
|
|
199
|
+
throw new Error("Invalid revalidateAll");
|
|
200
|
+
}
|
|
201
|
+
const paths = body.paths ?? (body.path === void 0 ? [] : [body.path]);
|
|
202
|
+
const tags = body.tags ?? (body.tag === void 0 ? [] : [body.tag]);
|
|
203
|
+
if (!Array.isArray(paths) || paths.length > SEO_REVALIDATION_MAX_ITEMS || !paths.every(isLocalPath)) {
|
|
204
|
+
throw new Error("Invalid paths");
|
|
205
|
+
}
|
|
206
|
+
if (!Array.isArray(tags) || tags.length > SEO_REVALIDATION_MAX_ITEMS || !tags.every(isCacheTag)) {
|
|
207
|
+
throw new Error("Invalid tags");
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
paths: [...new Set(paths)],
|
|
211
|
+
tags: [...new Set(tags)],
|
|
212
|
+
revalidateAll: body.revalidateAll === true
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function validateExtraPaths(paths) {
|
|
216
|
+
for (const path of paths ?? []) {
|
|
217
|
+
if (!isLocalPath(path)) {
|
|
218
|
+
throw new TypeError(`createSeoRevalidationHandler: extraPaths must be local paths like "/work", got ${JSON.stringify(path)}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return paths ?? [];
|
|
222
|
+
}
|
|
223
|
+
function normalizePublicationBasePath(value) {
|
|
224
|
+
if (value === void 0) return void 0;
|
|
225
|
+
const trimmed = value.replace(/\/+$/, "");
|
|
226
|
+
if (!trimmed || !isLocalPath(trimmed)) {
|
|
227
|
+
throw new TypeError(`createSeoRevalidationHandler: publicationBasePath must be a local path like "/insights", got ${JSON.stringify(value)}`);
|
|
228
|
+
}
|
|
229
|
+
return trimmed;
|
|
230
|
+
}
|
|
231
|
+
function createSeoRevalidationHandler({
|
|
232
|
+
secret: secretOption,
|
|
233
|
+
revalidatePath,
|
|
234
|
+
revalidateTag,
|
|
235
|
+
publicationBasePath,
|
|
236
|
+
extraPaths: extraPathsOption,
|
|
237
|
+
extendPayload
|
|
238
|
+
}) {
|
|
239
|
+
const publicationRoot = normalizePublicationBasePath(publicationBasePath);
|
|
240
|
+
const alwaysPaths = validateExtraPaths(extraPathsOption);
|
|
241
|
+
return async (request) => {
|
|
242
|
+
const secret = (typeof secretOption === "function" ? secretOption() : secretOption) ?? "";
|
|
243
|
+
const supplied = request.headers.get("authorization")?.match(/^Bearer (.+)$/i)?.[1];
|
|
244
|
+
if (!chunkYMZNEDFP_js.secretsMatch(supplied, secret)) return noStoreJson({ error: "Unauthorized" }, 401);
|
|
245
|
+
let payload;
|
|
246
|
+
try {
|
|
247
|
+
const bytes = await chunkYMZNEDFP_js.readCappedBody(request, SEO_REVALIDATION_MAX_BODY_BYTES);
|
|
248
|
+
if (!bytes) return noStoreJson({ error: "Payload too large" }, 413);
|
|
249
|
+
const body = JSON.parse(new TextDecoder().decode(bytes));
|
|
250
|
+
payload = parseSeoRevalidationPayload(body);
|
|
251
|
+
if (extendPayload) {
|
|
252
|
+
const extended = extendPayload(payload, body);
|
|
253
|
+
payload = parseSeoRevalidationPayload({
|
|
254
|
+
paths: extended.paths,
|
|
255
|
+
tags: extended.tags,
|
|
256
|
+
revalidateAll: extended.revalidateAll
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
} catch {
|
|
260
|
+
return noStoreJson({ error: "Provide valid local paths, cache tags or revalidateAll." }, 400);
|
|
261
|
+
}
|
|
262
|
+
const all = payload.revalidateAll || !payload.paths.length && payload.tags.includes("seo");
|
|
263
|
+
const pagePaths = publicationRoot ? payload.paths.flatMap(
|
|
264
|
+
(path) => path === LEGACY_PUBLICATION_PATH || path.startsWith(`${LEGACY_PUBLICATION_PATH}/`) ? [path, `${publicationRoot}${path.slice(LEGACY_PUBLICATION_PATH.length)}`] : [path]
|
|
265
|
+
) : payload.paths;
|
|
266
|
+
const publicationPaths = publicationRoot ? [publicationRoot, `${publicationRoot}/rss.xml`, `${publicationRoot}/feed.xml`] : [];
|
|
267
|
+
const extraPaths = [
|
|
268
|
+
.../* @__PURE__ */ new Set(["/sitemap.xml", ...publicationPaths, ...alwaysPaths, ...all ? ["/"] : pagePaths])
|
|
269
|
+
];
|
|
270
|
+
const llmsHandler = createLlmsRevalidateHandler(secret, {
|
|
271
|
+
extraPaths,
|
|
272
|
+
revalidatePath: (path) => revalidatePath(path, path === "/" && all ? "layout" : void 0)
|
|
273
|
+
});
|
|
274
|
+
try {
|
|
275
|
+
const result = await llmsHandler(request);
|
|
276
|
+
if (!result.ok) return noStoreJson({ error: "Revalidation failed" }, 500);
|
|
277
|
+
for (const tag of payload.tags) revalidateTag(tag, { expire: 0 });
|
|
278
|
+
return noStoreJson({ revalidated: ["/llms.txt", "/llms-full.txt", ...extraPaths], tags: payload.tags });
|
|
279
|
+
} catch {
|
|
280
|
+
return noStoreJson({ error: "Revalidation failed" }, 500);
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
175
285
|
Object.defineProperty(exports, "buildAiDiscoveryHeaders", {
|
|
176
286
|
enumerable: true,
|
|
177
287
|
get: function () { return chunkWS7MH3GD_js.buildAiDiscoveryHeaders; }
|
|
@@ -258,39 +368,39 @@ Object.defineProperty(exports, "getSpeakableSelectorsForPage", {
|
|
|
258
368
|
});
|
|
259
369
|
Object.defineProperty(exports, "generateLLMsFullTxt", {
|
|
260
370
|
enumerable: true,
|
|
261
|
-
get: function () { return
|
|
371
|
+
get: function () { return chunkOIPG3EGM_js.generateLLMsFullTxt; }
|
|
262
372
|
});
|
|
263
373
|
Object.defineProperty(exports, "generateLLMsTxt", {
|
|
264
374
|
enumerable: true,
|
|
265
|
-
get: function () { return
|
|
375
|
+
get: function () { return chunkOIPG3EGM_js.generateLLMsTxt; }
|
|
266
376
|
});
|
|
267
377
|
Object.defineProperty(exports, "getBusinessInfo", {
|
|
268
378
|
enumerable: true,
|
|
269
|
-
get: function () { return
|
|
379
|
+
get: function () { return chunkOIPG3EGM_js.getBusinessInfo; }
|
|
270
380
|
});
|
|
271
381
|
Object.defineProperty(exports, "getFAQItems", {
|
|
272
382
|
enumerable: true,
|
|
273
|
-
get: function () { return
|
|
383
|
+
get: function () { return chunkOIPG3EGM_js.getFAQItems; }
|
|
274
384
|
});
|
|
275
385
|
Object.defineProperty(exports, "getLLMsData", {
|
|
276
386
|
enumerable: true,
|
|
277
|
-
get: function () { return
|
|
387
|
+
get: function () { return chunkOIPG3EGM_js.getLLMsData; }
|
|
278
388
|
});
|
|
279
389
|
Object.defineProperty(exports, "getOptimizedLLMsTxt", {
|
|
280
390
|
enumerable: true,
|
|
281
|
-
get: function () { return
|
|
391
|
+
get: function () { return chunkOIPG3EGM_js.getOptimizedLLMsTxt; }
|
|
282
392
|
});
|
|
283
393
|
Object.defineProperty(exports, "getPageSummaries", {
|
|
284
394
|
enumerable: true,
|
|
285
|
-
get: function () { return
|
|
395
|
+
get: function () { return chunkOIPG3EGM_js.getPageSummaries; }
|
|
286
396
|
});
|
|
287
397
|
Object.defineProperty(exports, "getServices", {
|
|
288
398
|
enumerable: true,
|
|
289
|
-
get: function () { return
|
|
399
|
+
get: function () { return chunkOIPG3EGM_js.getServices; }
|
|
290
400
|
});
|
|
291
401
|
Object.defineProperty(exports, "writeLLMsTxtToPublic", {
|
|
292
402
|
enumerable: true,
|
|
293
|
-
get: function () { return
|
|
403
|
+
get: function () { return chunkOIPG3EGM_js.writeLLMsTxtToPublic; }
|
|
294
404
|
});
|
|
295
405
|
Object.defineProperty(exports, "LLMS_DISCLAIMER_MAX_LENGTH", {
|
|
296
406
|
enumerable: true,
|
|
@@ -324,10 +434,15 @@ Object.defineProperty(exports, "sanitizePrimaryLanguageTag", {
|
|
|
324
434
|
enumerable: true,
|
|
325
435
|
get: function () { return chunkH6AU3WKF_js.sanitizePrimaryLanguageTag; }
|
|
326
436
|
});
|
|
437
|
+
exports.LEGACY_PUBLICATION_PATH = LEGACY_PUBLICATION_PATH;
|
|
438
|
+
exports.SEO_REVALIDATION_MAX_BODY_BYTES = SEO_REVALIDATION_MAX_BODY_BYTES;
|
|
439
|
+
exports.SEO_REVALIDATION_MAX_ITEMS = SEO_REVALIDATION_MAX_ITEMS;
|
|
327
440
|
exports.createLLMsFullTxtHandler = createLLMsFullTxtHandler;
|
|
328
441
|
exports.createLLMsTxtHandler = createLLMsTxtHandler;
|
|
329
442
|
exports.createLlmsRevalidateHandler = createLlmsRevalidateHandler;
|
|
443
|
+
exports.createSeoRevalidationHandler = createSeoRevalidationHandler;
|
|
330
444
|
exports.formatSourcedFactsSection = formatSourcedFactsSection;
|
|
331
445
|
exports.llmsResponseHeaders = llmsResponseHeaders;
|
|
446
|
+
exports.parseSeoRevalidationPayload = parseSeoRevalidationPayload;
|
|
332
447
|
//# sourceMappingURL=index.js.map
|
|
333
448
|
//# sourceMappingURL=index.js.map
|
package/dist/llms/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/llms/handlers.ts","../../src/llms/sourcedFacts.ts","../../src/llms/revalidate.ts"],"names":["createHash","join","existsSync","markdown","readFileSync","generateLLMsTxt","generateLLMsFullTxt"],"mappings":"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","file":"index.js","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":["createHash","join","existsSync","markdown","readFileSync","generateLLMsTxt","generateLLMsFullTxt","secretsMatch","readCappedBody"],"mappings":"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","file":"index.js","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"]}
|
package/dist/llms/index.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
export { buildAiDiscoveryHeaders, wantsLlmsDiscoveryLink } from '../chunk-DA7IANFG.mjs';
|
|
2
2
|
export { AI_RETRIEVAL_CRAWLERS, AI_TRAINING_CRAWLERS, buildAiCrawlerRules, createRobotsTxtHandler, formatContentSignals } from '../chunk-HPGPOUHU.mjs';
|
|
3
3
|
export { AEOBlock, AEOCitedContent, AEOClaim, AEOComparison, AEODefinition, AEOEntity, AEOProvenanceList, AEOStep, AEOSteps, AEOSummary, DEFAULT_SPEAKABLE_SELECTORS, SpeakableSchema, createSpeakableSchema, getSpeakableSelectorsForPage } from '../chunk-3T2CVHFO.mjs';
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import { secretsMatch, readCappedBody } from '../chunk-Z2URIOKG.mjs';
|
|
5
|
+
import { generateLLMsTxt, generateLLMsFullTxt } from '../chunk-H4TSADY3.mjs';
|
|
6
|
+
export { generateLLMsFullTxt, generateLLMsTxt, getBusinessInfo, getFAQItems, getLLMsData, getOptimizedLLMsTxt, getPageSummaries, getServices, writeLLMsTxtToPublic } from '../chunk-H4TSADY3.mjs';
|
|
6
7
|
import '../chunk-5YVQWLSQ.mjs';
|
|
7
|
-
import '../chunk-
|
|
8
|
+
import '../chunk-6C47333H.mjs';
|
|
8
9
|
import '../chunk-BQP6JIE2.mjs';
|
|
9
10
|
export { LLMS_DISCLAIMER_MAX_LENGTH, LLMS_PUBLIC_SUMMARY_MAX_LENGTH, LLM_GEO_CONTRACT_VERSION, MANAGED_LLM_SCHEMA_KNOWN_KEYS, pickManagedLlmSchemaForJsonLd, sanitizeLlmsDisclaimerLine, sanitizeLlmsPublicSummary, sanitizePrimaryLanguageTag } from '../chunk-PENNTSJE.mjs';
|
|
10
11
|
import '../chunk-662YHKWW.mjs';
|
|
@@ -137,7 +138,7 @@ function createLlmsRevalidateHandler(secret, options) {
|
|
|
137
138
|
const url = new URL(request.url);
|
|
138
139
|
const tokenFromQuery = url.searchParams.get("secret");
|
|
139
140
|
const token = authHeader?.replace(/^Bearer\s+/i, "") || tokenFromQuery;
|
|
140
|
-
if (!
|
|
141
|
+
if (!secretsMatch(token, secret)) {
|
|
141
142
|
return new Response(JSON.stringify({ error: "Invalid secret" }), {
|
|
142
143
|
status: 401,
|
|
143
144
|
headers: { "Content-Type": "application/json" }
|
|
@@ -171,6 +172,115 @@ function createLlmsRevalidateHandler(secret, options) {
|
|
|
171
172
|
};
|
|
172
173
|
}
|
|
173
174
|
|
|
174
|
-
|
|
175
|
+
// src/llms/seo-revalidate.ts
|
|
176
|
+
var SEO_REVALIDATION_MAX_BODY_BYTES = 16 * 1024;
|
|
177
|
+
var SEO_REVALIDATION_MAX_ITEMS = 100;
|
|
178
|
+
var LEGACY_PUBLICATION_PATH = "/blog";
|
|
179
|
+
function noStoreJson(body, status = 200) {
|
|
180
|
+
return Response.json(body, { status, headers: { "Cache-Control": "no-store" } });
|
|
181
|
+
}
|
|
182
|
+
function isLocalPath(value) {
|
|
183
|
+
if (typeof value !== "string" || value.length > 1024) return false;
|
|
184
|
+
try {
|
|
185
|
+
const path = decodeURIComponent(value);
|
|
186
|
+
return path.startsWith("/") && !path.startsWith("//") && !/[\\\s?#[\]:\x00-\x1f]/.test(path) && !path.split("/").some((part) => part === "." || part === "..");
|
|
187
|
+
} catch {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function isCacheTag(value) {
|
|
192
|
+
return typeof value === "string" && value.length > 0 && value.length <= 256 && !/[\x00-\x1f]/.test(value);
|
|
193
|
+
}
|
|
194
|
+
function parseSeoRevalidationPayload(value) {
|
|
195
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid payload");
|
|
196
|
+
const body = value;
|
|
197
|
+
if (body.revalidateAll !== void 0 && typeof body.revalidateAll !== "boolean") {
|
|
198
|
+
throw new Error("Invalid revalidateAll");
|
|
199
|
+
}
|
|
200
|
+
const paths = body.paths ?? (body.path === void 0 ? [] : [body.path]);
|
|
201
|
+
const tags = body.tags ?? (body.tag === void 0 ? [] : [body.tag]);
|
|
202
|
+
if (!Array.isArray(paths) || paths.length > SEO_REVALIDATION_MAX_ITEMS || !paths.every(isLocalPath)) {
|
|
203
|
+
throw new Error("Invalid paths");
|
|
204
|
+
}
|
|
205
|
+
if (!Array.isArray(tags) || tags.length > SEO_REVALIDATION_MAX_ITEMS || !tags.every(isCacheTag)) {
|
|
206
|
+
throw new Error("Invalid tags");
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
paths: [...new Set(paths)],
|
|
210
|
+
tags: [...new Set(tags)],
|
|
211
|
+
revalidateAll: body.revalidateAll === true
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function validateExtraPaths(paths) {
|
|
215
|
+
for (const path of paths ?? []) {
|
|
216
|
+
if (!isLocalPath(path)) {
|
|
217
|
+
throw new TypeError(`createSeoRevalidationHandler: extraPaths must be local paths like "/work", got ${JSON.stringify(path)}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return paths ?? [];
|
|
221
|
+
}
|
|
222
|
+
function normalizePublicationBasePath(value) {
|
|
223
|
+
if (value === void 0) return void 0;
|
|
224
|
+
const trimmed = value.replace(/\/+$/, "");
|
|
225
|
+
if (!trimmed || !isLocalPath(trimmed)) {
|
|
226
|
+
throw new TypeError(`createSeoRevalidationHandler: publicationBasePath must be a local path like "/insights", got ${JSON.stringify(value)}`);
|
|
227
|
+
}
|
|
228
|
+
return trimmed;
|
|
229
|
+
}
|
|
230
|
+
function createSeoRevalidationHandler({
|
|
231
|
+
secret: secretOption,
|
|
232
|
+
revalidatePath,
|
|
233
|
+
revalidateTag,
|
|
234
|
+
publicationBasePath,
|
|
235
|
+
extraPaths: extraPathsOption,
|
|
236
|
+
extendPayload
|
|
237
|
+
}) {
|
|
238
|
+
const publicationRoot = normalizePublicationBasePath(publicationBasePath);
|
|
239
|
+
const alwaysPaths = validateExtraPaths(extraPathsOption);
|
|
240
|
+
return async (request) => {
|
|
241
|
+
const secret = (typeof secretOption === "function" ? secretOption() : secretOption) ?? "";
|
|
242
|
+
const supplied = request.headers.get("authorization")?.match(/^Bearer (.+)$/i)?.[1];
|
|
243
|
+
if (!secretsMatch(supplied, secret)) return noStoreJson({ error: "Unauthorized" }, 401);
|
|
244
|
+
let payload;
|
|
245
|
+
try {
|
|
246
|
+
const bytes = await readCappedBody(request, SEO_REVALIDATION_MAX_BODY_BYTES);
|
|
247
|
+
if (!bytes) return noStoreJson({ error: "Payload too large" }, 413);
|
|
248
|
+
const body = JSON.parse(new TextDecoder().decode(bytes));
|
|
249
|
+
payload = parseSeoRevalidationPayload(body);
|
|
250
|
+
if (extendPayload) {
|
|
251
|
+
const extended = extendPayload(payload, body);
|
|
252
|
+
payload = parseSeoRevalidationPayload({
|
|
253
|
+
paths: extended.paths,
|
|
254
|
+
tags: extended.tags,
|
|
255
|
+
revalidateAll: extended.revalidateAll
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
} catch {
|
|
259
|
+
return noStoreJson({ error: "Provide valid local paths, cache tags or revalidateAll." }, 400);
|
|
260
|
+
}
|
|
261
|
+
const all = payload.revalidateAll || !payload.paths.length && payload.tags.includes("seo");
|
|
262
|
+
const pagePaths = publicationRoot ? payload.paths.flatMap(
|
|
263
|
+
(path) => path === LEGACY_PUBLICATION_PATH || path.startsWith(`${LEGACY_PUBLICATION_PATH}/`) ? [path, `${publicationRoot}${path.slice(LEGACY_PUBLICATION_PATH.length)}`] : [path]
|
|
264
|
+
) : payload.paths;
|
|
265
|
+
const publicationPaths = publicationRoot ? [publicationRoot, `${publicationRoot}/rss.xml`, `${publicationRoot}/feed.xml`] : [];
|
|
266
|
+
const extraPaths = [
|
|
267
|
+
.../* @__PURE__ */ new Set(["/sitemap.xml", ...publicationPaths, ...alwaysPaths, ...all ? ["/"] : pagePaths])
|
|
268
|
+
];
|
|
269
|
+
const llmsHandler = createLlmsRevalidateHandler(secret, {
|
|
270
|
+
extraPaths,
|
|
271
|
+
revalidatePath: (path) => revalidatePath(path, path === "/" && all ? "layout" : void 0)
|
|
272
|
+
});
|
|
273
|
+
try {
|
|
274
|
+
const result = await llmsHandler(request);
|
|
275
|
+
if (!result.ok) return noStoreJson({ error: "Revalidation failed" }, 500);
|
|
276
|
+
for (const tag of payload.tags) revalidateTag(tag, { expire: 0 });
|
|
277
|
+
return noStoreJson({ revalidated: ["/llms.txt", "/llms-full.txt", ...extraPaths], tags: payload.tags });
|
|
278
|
+
} catch {
|
|
279
|
+
return noStoreJson({ error: "Revalidation failed" }, 500);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export { LEGACY_PUBLICATION_PATH, SEO_REVALIDATION_MAX_BODY_BYTES, SEO_REVALIDATION_MAX_ITEMS, createLLMsFullTxtHandler, createLLMsTxtHandler, createLlmsRevalidateHandler, createSeoRevalidationHandler, formatSourcedFactsSection, llmsResponseHeaders, parseSeoRevalidationPayload };
|
|
175
285
|
//# sourceMappingURL=index.mjs.map
|
|
176
286
|
//# sourceMappingURL=index.mjs.map
|
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":"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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\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
|
/**
|