@sonordev/site-kit 5.8.3 → 6.0.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/README.md +11 -11
- package/agent-manifest.json +17 -11
- package/dist/{AnalyticsProvider-IGOD64MY.mjs → AnalyticsProvider-6QVM2B6O.mjs} +6 -6
- package/dist/{AnalyticsProvider-IGOD64MY.mjs.map → AnalyticsProvider-6QVM2B6O.mjs.map} +1 -1
- package/dist/{AnalyticsProvider-DPXR6FSV.js → AnalyticsProvider-AZQXLRWN.js} +10 -10
- package/dist/{AnalyticsProvider-DPXR6FSV.js.map → AnalyticsProvider-AZQXLRWN.js.map} +1 -1
- package/dist/ArticleFaqSection-3KZ24MFS.js +13 -0
- package/dist/ArticleFaqSection-3KZ24MFS.js.map +1 -0
- package/dist/ArticleFaqSection-XZPXJVH4.mjs +11 -0
- package/dist/ArticleFaqSection-XZPXJVH4.mjs.map +1 -0
- package/dist/{BlogViewTracker-Q7Y4MN4B.mjs → ArticleViewTracker-AEWJMXQQ.mjs} +13 -13
- package/dist/ArticleViewTracker-AEWJMXQQ.mjs.map +1 -0
- package/dist/{BlogViewTracker-S3CSRBIZ.js → ArticleViewTracker-CFBTQ46B.js} +16 -16
- package/dist/ArticleViewTracker-CFBTQ46B.js.map +1 -0
- package/dist/ChatWidget-6626OPXO.mjs +15 -0
- package/dist/{ChatWidget-G3KOUDX5.mjs.map → ChatWidget-6626OPXO.mjs.map} +1 -1
- package/dist/ChatWidget-U24AYLXF.js +24 -0
- package/dist/{ChatWidget-76R7NOWD.js.map → ChatWidget-U24AYLXF.js.map} +1 -1
- package/dist/EngageWidget-TR47W2ZI.mjs +12 -0
- package/dist/{EngageWidget-UJ47PQFG.mjs.map → EngageWidget-TR47W2ZI.mjs.map} +1 -1
- package/dist/EngageWidget-XUTDFRF3.js +21 -0
- package/dist/{EngageWidget-HD4F2ZVE.js.map → EngageWidget-XUTDFRF3.js.map} +1 -1
- package/dist/{FileField-WNMEA3PK.mjs → FileField-DNNC5IPS.mjs} +5 -5
- package/dist/{FileField-WNMEA3PK.mjs.map → FileField-DNNC5IPS.mjs.map} +1 -1
- package/dist/{FileField-FV6WSZMX.js → FileField-R4RQG77G.js} +6 -6
- package/dist/{FileField-FV6WSZMX.js.map → FileField-R4RQG77G.js.map} +1 -1
- package/dist/{FormSpotlight-2VRQXEFI.js → FormSpotlight-7ILRAD3W.js} +9 -9
- package/dist/{FormSpotlight-2VRQXEFI.js.map → FormSpotlight-7ILRAD3W.js.map} +1 -1
- package/dist/{FormSpotlight-NGWEPGI4.mjs → FormSpotlight-MXY4I46H.mjs} +3 -3
- package/dist/{FormSpotlight-NGWEPGI4.mjs.map → FormSpotlight-MXY4I46H.mjs.map} +1 -1
- package/dist/{FormStage-Q3KQVFVW.js → FormStage-CVD3I3SE.js} +9 -9
- package/dist/{FormStage-Q3KQVFVW.js.map → FormStage-CVD3I3SE.js.map} +1 -1
- package/dist/{FormStage-7L4BBOLC.mjs → FormStage-FDJGRGEU.mjs} +3 -3
- package/dist/{FormStage-7L4BBOLC.mjs.map → FormStage-FDJGRGEU.mjs.map} +1 -1
- package/dist/ManagedForm-5675CLWS.mjs +16 -0
- package/dist/{ManagedForm-Z7QE4KYI.mjs.map → ManagedForm-5675CLWS.mjs.map} +1 -1
- package/dist/ManagedForm-KYIK54SL.js +25 -0
- package/dist/{ManagedForm-4ZGU4R3G.js.map → ManagedForm-KYIK54SL.js.map} +1 -1
- package/dist/{ManagedNewsletterForm-4TVO2VL4.mjs → ManagedNewsletterForm-DFLDGNQU.mjs} +11 -11
- package/dist/ManagedNewsletterForm-DFLDGNQU.mjs.map +1 -0
- package/dist/{ManagedNewsletterForm-22EPZB7Z.js → ManagedNewsletterForm-IH3MRJOD.js} +13 -13
- package/dist/ManagedNewsletterForm-IH3MRJOD.js.map +1 -0
- package/dist/{SignalCore-2N7UZGF3.js → SignalCore-6LYIZFXK.js} +11 -11
- package/dist/{SignalCore-2N7UZGF3.js.map → SignalCore-6LYIZFXK.js.map} +1 -1
- package/dist/{SignalCore-57OK3DHA.mjs → SignalCore-ZH3R6XD3.mjs} +5 -5
- package/dist/{SignalCore-57OK3DHA.mjs.map → SignalCore-ZH3R6XD3.mjs.map} +1 -1
- package/dist/SitemapSync-PKBXWVI6.mjs +9 -0
- package/dist/{SitemapSync-WIUEYOBS.mjs.map → SitemapSync-PKBXWVI6.mjs.map} +1 -1
- package/dist/SitemapSync-PZ76V5MX.js +18 -0
- package/dist/{SitemapSync-GF4OKN4Z.js.map → SitemapSync-PZ76V5MX.js.map} +1 -1
- package/dist/analytics/index.js +9 -9
- package/dist/analytics/index.mjs +4 -4
- package/dist/{blog-styles-7AKRUMV7.mjs → article-styles-DACUAOVT.mjs} +124 -124
- package/dist/article-styles-DACUAOVT.mjs.map +1 -0
- package/dist/{blog-styles-6EY3BS5Y.js → article-styles-JHHTNHOK.js} +125 -125
- package/dist/article-styles-JHHTNHOK.js.map +1 -0
- package/dist/article-tables-EWXG4LNP.js +17 -0
- package/dist/article-tables-EWXG4LNP.js.map +1 -0
- package/dist/article-tables-OGE52LNT.mjs +4 -0
- package/dist/article-tables-OGE52LNT.mjs.map +1 -0
- package/dist/{blog/BlogPost.d.ts → articles/Article.d.ts} +18 -16
- package/dist/articles/Article.d.ts.map +1 -0
- package/dist/{blog/BlogFAQ.d.ts → articles/ArticleFAQ.d.ts} +6 -6
- package/dist/articles/ArticleFAQ.d.ts.map +1 -0
- package/dist/articles/ArticleFaqSection.d.ts +3 -0
- package/dist/articles/ArticleFaqSection.d.ts.map +1 -0
- package/dist/{blog/BlogList.d.ts → articles/ArticleList.d.ts} +16 -16
- package/dist/articles/ArticleList.d.ts.map +1 -0
- package/dist/{blog/BlogViewTracker.d.ts → articles/ArticleViewTracker.d.ts} +4 -4
- package/dist/articles/ArticleViewTracker.d.ts.map +1 -0
- package/dist/{blog → articles}/AuthorCard.d.ts +3 -3
- package/dist/articles/AuthorCard.d.ts.map +1 -0
- package/dist/{blog → articles}/AuthorPage.d.ts +1 -1
- package/dist/articles/AuthorPage.d.ts.map +1 -0
- package/dist/{blog → articles}/ClusterLandingPage.d.ts +6 -6
- package/dist/articles/ClusterLandingPage.d.ts.map +1 -0
- package/dist/{blog → articles}/ClusterNavigation.d.ts +5 -5
- package/dist/articles/ClusterNavigation.d.ts.map +1 -0
- package/dist/{blog → articles}/ManagedNewsletterForm.d.ts +1 -1
- package/dist/articles/ManagedNewsletterForm.d.ts.map +1 -0
- package/dist/{blog → articles}/NewsletterShell.d.ts +1 -1
- package/dist/articles/NewsletterShell.d.ts.map +1 -0
- package/dist/{blog → articles}/NewsletterWidget.d.ts +4 -4
- package/dist/articles/NewsletterWidget.d.ts.map +1 -0
- package/dist/{blog/BlogLayout.d.ts → articles/PublicationLayout.d.ts} +18 -18
- package/dist/articles/PublicationLayout.d.ts.map +1 -0
- package/dist/{blog/BlogSidebar.d.ts → articles/PublicationSidebar.d.ts} +8 -8
- package/dist/articles/PublicationSidebar.d.ts.map +1 -0
- package/dist/{blog → articles}/RelatedPosts.d.ts +6 -6
- package/dist/articles/RelatedPosts.d.ts.map +1 -0
- package/dist/{blog → articles}/ServiceCallout.d.ts +4 -4
- package/dist/articles/ServiceCallout.d.ts.map +1 -0
- package/dist/{blog → articles}/TableOfContents.d.ts +1 -1
- package/dist/articles/TableOfContents.d.ts.map +1 -0
- package/dist/{blog/blog-styles.d.ts → articles/article-styles.d.ts} +5 -5
- package/dist/articles/article-styles.d.ts.map +1 -0
- package/dist/{blog/blog-tables.d.ts → articles/article-tables.d.ts} +4 -4
- package/dist/articles/article-tables.d.ts.map +1 -0
- package/dist/articles/artwork.d.ts +14 -0
- package/dist/articles/artwork.d.ts.map +1 -0
- package/dist/{blog → articles}/author-schema.d.ts +6 -5
- package/dist/articles/author-schema.d.ts.map +1 -0
- package/dist/{blog → articles}/author-social.d.ts +2 -2
- package/dist/articles/author-social.d.ts.map +1 -0
- package/dist/{blog → articles}/excerpt.d.ts +2 -2
- package/dist/articles/excerpt.d.ts.map +1 -0
- package/dist/{blog → articles}/index.d.ts +26 -31
- package/dist/articles/index.d.ts.map +1 -0
- package/dist/{blog → articles}/index.js +39 -73
- package/dist/articles/index.js.map +1 -0
- package/dist/{blog → articles}/index.mjs +16 -17
- package/dist/articles/index.mjs.map +1 -0
- package/dist/articles/news-sitemap.d.ts +47 -0
- package/dist/articles/news-sitemap.d.ts.map +1 -0
- package/dist/{blog/processBlogHtml.d.ts → articles/processArticleHtml.d.ts} +4 -4
- package/dist/articles/processArticleHtml.d.ts.map +1 -0
- package/dist/articles/public-reads.d.ts +27 -0
- package/dist/articles/public-reads.d.ts.map +1 -0
- package/dist/{blog → articles}/reading-time.d.ts +2 -2
- package/dist/articles/reading-time.d.ts.map +1 -0
- package/dist/{blog → articles}/routes.d.ts +16 -12
- package/dist/articles/routes.d.ts.map +1 -0
- package/dist/{blog → articles}/server-core.d.ts +94 -81
- package/dist/articles/server-core.d.ts.map +1 -0
- package/dist/articles/server-ui.d.ts +29 -0
- package/dist/articles/server-ui.d.ts.map +1 -0
- package/dist/{chunk-W2U5SLMY.js → articles/server-ui.js} +378 -499
- package/dist/articles/server-ui.js.map +1 -0
- package/dist/{chunk-MWKI6ZIS.mjs → articles/server-ui.mjs} +328 -474
- package/dist/articles/server-ui.mjs.map +1 -0
- package/dist/{blog → articles}/server.d.ts +94 -81
- package/dist/articles/server.d.ts.map +1 -0
- package/dist/articles/server.js +236 -0
- package/dist/articles/server.mjs +11 -0
- package/dist/{blog → articles}/types.d.ts +34 -34
- package/dist/articles/types.d.ts.map +1 -0
- package/dist/articles/widget-styles.d.ts +9 -0
- package/dist/articles/widget-styles.d.ts.map +1 -0
- package/dist/brand/extract-brand-profile.d.ts +1 -1
- package/dist/{chunk-PIEHBSCN.js → chunk-254IW4FC.js} +9 -9
- package/dist/{chunk-PIEHBSCN.js.map → chunk-254IW4FC.js.map} +1 -1
- package/dist/chunk-2KRAY5NC.mjs +6 -0
- package/dist/{chunk-5FETEH3Y.mjs.map → chunk-2KRAY5NC.mjs.map} +1 -1
- package/dist/{chunk-MQS6AF5K.mjs → chunk-2WEBUZNP.mjs} +3 -3
- package/dist/{chunk-MQS6AF5K.mjs.map → chunk-2WEBUZNP.mjs.map} +1 -1
- package/dist/{chunk-4C5CZJIN.mjs → chunk-2YSB4FIC.mjs} +4 -4
- package/dist/{chunk-4C5CZJIN.mjs.map → chunk-2YSB4FIC.mjs.map} +1 -1
- package/dist/{chunk-I7HOMMIK.js → chunk-3GHPFLEH.js} +15 -15
- package/dist/{chunk-I7HOMMIK.js.map → chunk-3GHPFLEH.js.map} +1 -1
- package/dist/{chunk-CY2RFEW6.js → chunk-3LCJJ6Y6.js} +3 -3
- package/dist/{chunk-CY2RFEW6.js.map → chunk-3LCJJ6Y6.js.map} +1 -1
- package/dist/{chunk-QOOLMSBY.mjs → chunk-3OCXVTN5.mjs} +3 -3
- package/dist/{chunk-QOOLMSBY.mjs.map → chunk-3OCXVTN5.mjs.map} +1 -1
- package/dist/{chunk-AJBOEV4I.mjs → chunk-3T2CVHFO.mjs} +3 -3
- package/dist/chunk-3T2CVHFO.mjs.map +1 -0
- package/dist/{chunk-NR7YZQRR.js → chunk-3U5SVB4W.js} +4 -4
- package/dist/{chunk-NR7YZQRR.js.map → chunk-3U5SVB4W.js.map} +1 -1
- package/dist/{chunk-I3ND4XYW.js → chunk-4BEGKFMN.js} +8 -8
- package/dist/{chunk-I3ND4XYW.js.map → chunk-4BEGKFMN.js.map} +1 -1
- package/dist/{chunk-YFX3Q56O.mjs → chunk-4KAMU5HD.mjs} +3 -3
- package/dist/chunk-4KAMU5HD.mjs.map +1 -0
- package/dist/{chunk-ZPLN537Q.mjs → chunk-4WCA2HM6.mjs} +2 -2
- package/dist/chunk-4WCA2HM6.mjs.map +1 -0
- package/dist/{chunk-6CCS2EGF.js → chunk-5DSUPTBU.js} +13 -13
- package/dist/{chunk-6CCS2EGF.js.map → chunk-5DSUPTBU.js.map} +1 -1
- package/dist/{chunk-GHECYDSZ.mjs → chunk-5F2FIM5A.mjs} +16 -15
- package/dist/chunk-5F2FIM5A.mjs.map +1 -0
- package/dist/{chunk-Y5QZPUL3.mjs → chunk-5YVQWLSQ.mjs} +5 -5
- package/dist/chunk-5YVQWLSQ.mjs.map +1 -0
- package/dist/chunk-6DCAMW62.mjs +55 -0
- package/dist/chunk-6DCAMW62.mjs.map +1 -0
- package/dist/{chunk-Q2NBD54V.js → chunk-7THQ4UW3.js} +2 -2
- package/dist/chunk-7THQ4UW3.js.map +1 -0
- package/dist/{chunk-LGOQ65JQ.mjs → chunk-7TKSRUQU.mjs} +7 -7
- package/dist/{chunk-LGOQ65JQ.mjs.map → chunk-7TKSRUQU.mjs.map} +1 -1
- package/dist/{chunk-4TLFZ6NR.mjs → chunk-AS2HUIDW.mjs} +3 -3
- package/dist/chunk-AS2HUIDW.mjs.map +1 -0
- package/dist/{chunk-YN7QHLWF.js → chunk-BBBJHYPH.js} +5 -5
- package/dist/chunk-BBBJHYPH.js.map +1 -0
- package/dist/{chunk-2TS4ALOA.js → chunk-BZG34WHF.js} +14 -14
- package/dist/{chunk-2TS4ALOA.js.map → chunk-BZG34WHF.js.map} +1 -1
- package/dist/{chunk-4LYW6PMU.mjs → chunk-C2CTLLIO.mjs} +3 -3
- package/dist/chunk-C2CTLLIO.mjs.map +1 -0
- package/dist/{chunk-O237KKCX.mjs → chunk-C5VITBN7.mjs} +5 -5
- package/dist/chunk-C5VITBN7.mjs.map +1 -0
- package/dist/{chunk-75A4KY4X.mjs → chunk-CFRJ2MK5.mjs} +2 -2
- package/dist/chunk-CFRJ2MK5.mjs.map +1 -0
- package/dist/{chunk-G2WC3CXW.js → chunk-CLO3K7PF.js} +2 -2
- package/dist/chunk-CLO3K7PF.js.map +1 -0
- package/dist/{chunk-SFC3OWSE.mjs → chunk-CSSNZGTU.mjs} +4 -4
- package/dist/{chunk-SFC3OWSE.mjs.map → chunk-CSSNZGTU.mjs.map} +1 -1
- package/dist/{chunk-7VZYP4K5.js → chunk-CZGGUPPQ.js} +15 -15
- package/dist/{chunk-7VZYP4K5.js.map → chunk-CZGGUPPQ.js.map} +1 -1
- package/dist/{chunk-FTB5LAD3.js → chunk-D47W5DE4.js} +4 -4
- package/dist/{chunk-FTB5LAD3.js.map → chunk-D47W5DE4.js.map} +1 -1
- package/dist/{chunk-AK6JZ3OE.mjs → chunk-EDLNMGQC.mjs} +3 -3
- package/dist/{chunk-DO6ATNLN.js.map → chunk-EDLNMGQC.mjs.map} +1 -1
- package/dist/chunk-EL74YDNX.js +8 -0
- package/dist/{chunk-7R6H3GQ4.js.map → chunk-EL74YDNX.js.map} +1 -1
- package/dist/{chunk-HX6ZG3PP.js → chunk-EMHSHAVW.js} +3 -3
- package/dist/chunk-EMHSHAVW.js.map +1 -0
- package/dist/{chunk-7TC3GRXQ.js → chunk-G6KQ3C3W.js} +25 -23
- package/dist/chunk-G6KQ3C3W.js.map +1 -0
- package/dist/{chunk-HEOLGDUX.mjs → chunk-GQ3SDRL6.mjs} +4 -4
- package/dist/{chunk-HEOLGDUX.mjs.map → chunk-GQ3SDRL6.mjs.map} +1 -1
- package/dist/{chunk-IIHIRHSW.js → chunk-HFCQGSD2.js} +18 -18
- package/dist/{chunk-IIHIRHSW.js.map → chunk-HFCQGSD2.js.map} +1 -1
- package/dist/{chunk-XZO5I4RS.mjs → chunk-HIZJD7TU.mjs} +3 -3
- package/dist/{chunk-XZO5I4RS.mjs.map → chunk-HIZJD7TU.mjs.map} +1 -1
- package/dist/{chunk-UQY5KC64.mjs → chunk-HOOVYWTO.mjs} +4 -4
- package/dist/{chunk-UQY5KC64.mjs.map → chunk-HOOVYWTO.mjs.map} +1 -1
- package/dist/{chunk-NBT4L2D7.js → chunk-HX5JPNMN.js} +8 -8
- package/dist/{chunk-NBT4L2D7.js.map → chunk-HX5JPNMN.js.map} +1 -1
- package/dist/chunk-HXHRS3PX.mjs +349 -0
- package/dist/chunk-HXHRS3PX.mjs.map +1 -0
- package/dist/{chunk-XBX2K3GA.js → chunk-IBJOGXQI.js} +4 -4
- package/dist/{chunk-XBX2K3GA.js.map → chunk-IBJOGXQI.js.map} +1 -1
- package/dist/{chunk-3OO5EDKD.mjs → chunk-JMNBU2RG.mjs} +9 -9
- package/dist/{chunk-3OO5EDKD.mjs.map → chunk-JMNBU2RG.mjs.map} +1 -1
- package/dist/{chunk-R7GHP3FY.js → chunk-JONWE26J.js} +3 -3
- package/dist/chunk-JONWE26J.js.map +1 -0
- package/dist/{chunk-PQYQHZWV.mjs → chunk-JSXLTZUP.mjs} +6 -6
- package/dist/{chunk-PQYQHZWV.mjs.map → chunk-JSXLTZUP.mjs.map} +1 -1
- package/dist/{chunk-FD72QL42.mjs → chunk-JXCMCASE.mjs} +3 -3
- package/dist/{chunk-FD72QL42.mjs.map → chunk-JXCMCASE.mjs.map} +1 -1
- package/dist/{chunk-TR3F3CLV.js → chunk-KA4NNWOK.js} +10 -10
- package/dist/{chunk-TR3F3CLV.js.map → chunk-KA4NNWOK.js.map} +1 -1
- package/dist/{chunk-K4TCEAUX.js → chunk-KR7ATPAN.js} +5 -5
- package/dist/chunk-KR7ATPAN.js.map +1 -0
- package/dist/{chunk-JLVCWC6A.mjs → chunk-KZVSB5WE.mjs} +2 -2
- package/dist/chunk-KZVSB5WE.mjs.map +1 -0
- package/dist/chunk-LWCAZDY2.js +355 -0
- package/dist/chunk-LWCAZDY2.js.map +1 -0
- package/dist/{chunk-5HFD36AN.js → chunk-MCG4BSRW.js} +5 -5
- package/dist/{chunk-5HFD36AN.js.map → chunk-MCG4BSRW.js.map} +1 -1
- package/dist/{chunk-S2Y7LVQ2.js → chunk-MIYGP34I.js} +14 -14
- package/dist/{chunk-S2Y7LVQ2.js.map → chunk-MIYGP34I.js.map} +1 -1
- package/dist/{chunk-PI3FHABB.js → chunk-MS2EE6LT.js} +10 -10
- package/dist/{chunk-PI3FHABB.js.map → chunk-MS2EE6LT.js.map} +1 -1
- package/dist/{chunk-QQQAW6WY.js → chunk-N6CB7MK7.js} +6 -6
- package/dist/{chunk-QQQAW6WY.js.map → chunk-N6CB7MK7.js.map} +1 -1
- package/dist/{chunk-LMEUYRRR.js → chunk-NAA5J2JM.js} +233 -120
- package/dist/chunk-NAA5J2JM.js.map +1 -0
- package/dist/{chunk-OE7CTIOU.mjs → chunk-NE6QGHFT.mjs} +4 -4
- package/dist/{chunk-OE7CTIOU.mjs.map → chunk-NE6QGHFT.mjs.map} +1 -1
- package/dist/{chunk-NCDF2RK4.mjs → chunk-NR52QHOD.mjs} +3 -3
- package/dist/{chunk-NCDF2RK4.mjs.map → chunk-NR52QHOD.mjs.map} +1 -1
- package/dist/{chunk-GQF6QAQS.js → chunk-OGNX6JT4.js} +5 -5
- package/dist/chunk-OGNX6JT4.js.map +1 -0
- package/dist/{chunk-FVW6FT6L.js → chunk-OSU4QA5M.js} +8 -8
- package/dist/{chunk-FVW6FT6L.js.map → chunk-OSU4QA5M.js.map} +1 -1
- package/dist/{chunk-UEUWC3PH.mjs → chunk-PJYTYM5G.mjs} +3 -3
- package/dist/{chunk-UEUWC3PH.mjs.map → chunk-PJYTYM5G.mjs.map} +1 -1
- package/dist/{chunk-DO6ATNLN.js → chunk-PRAHDLKT.js} +5 -5
- package/dist/chunk-PRAHDLKT.js.map +1 -0
- package/dist/{chunk-RFAAY3BW.mjs → chunk-QEM2F2DC.mjs} +3 -3
- package/dist/{chunk-RFAAY3BW.mjs.map → chunk-QEM2F2DC.mjs.map} +1 -1
- package/dist/{chunk-F3KA6V2Y.js → chunk-QOCLSJIU.js} +3 -3
- package/dist/{chunk-F3KA6V2Y.js.map → chunk-QOCLSJIU.js.map} +1 -1
- package/dist/{chunk-R5IPDAMM.mjs → chunk-QVBAAPNT.mjs} +3 -3
- package/dist/{chunk-R5IPDAMM.mjs.map → chunk-QVBAAPNT.mjs.map} +1 -1
- package/dist/{chunk-POJZA67T.mjs → chunk-RC4L4DV2.mjs} +4 -4
- package/dist/{chunk-POJZA67T.mjs.map → chunk-RC4L4DV2.mjs.map} +1 -1
- package/dist/{chunk-ZVI7MBVF.mjs → chunk-RDPI27TS.mjs} +12 -10
- package/dist/chunk-RDPI27TS.mjs.map +1 -0
- package/dist/{chunk-OEW2LFOV.js → chunk-RLUNEEGM.js} +4 -4
- package/dist/{chunk-OEW2LFOV.js.map → chunk-RLUNEEGM.js.map} +1 -1
- package/dist/{chunk-HBHS5VRN.mjs → chunk-RPCODOWG.mjs} +3 -3
- package/dist/{chunk-HBHS5VRN.mjs.map → chunk-RPCODOWG.mjs.map} +1 -1
- package/dist/{chunk-ROSYCNXG.mjs → chunk-RZGVNDO6.mjs} +4 -4
- package/dist/{chunk-ROSYCNXG.mjs.map → chunk-RZGVNDO6.mjs.map} +1 -1
- package/dist/{chunk-MYMZRHTC.js → chunk-SCRB5DZM.js} +8 -8
- package/dist/{chunk-MYMZRHTC.js.map → chunk-SCRB5DZM.js.map} +1 -1
- package/dist/{chunk-YQTE32QK.js → chunk-SG7CWD2R.js} +17 -16
- package/dist/chunk-SG7CWD2R.js.map +1 -0
- package/dist/{chunk-XEB77DQS.js → chunk-TBXWJJGT.js} +3 -3
- package/dist/chunk-TBXWJJGT.js.map +1 -0
- package/dist/{chunk-QXMWNIKL.mjs → chunk-TCCGP3FT.mjs} +3 -3
- package/dist/{chunk-QXMWNIKL.mjs.map → chunk-TCCGP3FT.mjs.map} +1 -1
- package/dist/{chunk-CZEIHFLW.js → chunk-TCZHRSWY.js} +4 -4
- package/dist/{chunk-CZEIHFLW.js.map → chunk-TCZHRSWY.js.map} +1 -1
- package/dist/{chunk-LB44BNJ2.js → chunk-TST2IKYO.js} +14 -14
- package/dist/{chunk-LB44BNJ2.js.map → chunk-TST2IKYO.js.map} +1 -1
- package/dist/{chunk-U2V542EO.mjs → chunk-UHYRVALN.mjs} +2 -2
- package/dist/{chunk-U2V542EO.mjs.map → chunk-UHYRVALN.mjs.map} +1 -1
- package/dist/{chunk-4L7OHRWB.mjs → chunk-UO6KTHBQ.mjs} +4 -4
- package/dist/{chunk-4L7OHRWB.mjs.map → chunk-UO6KTHBQ.mjs.map} +1 -1
- package/dist/{chunk-H5NBFNND.js → chunk-UZKXOZHR.js} +2 -2
- package/dist/chunk-UZKXOZHR.js.map +1 -0
- package/dist/{chunk-NMM27UOF.js → chunk-VINLIMAX.js} +3 -3
- package/dist/{chunk-NMM27UOF.js.map → chunk-VINLIMAX.js.map} +1 -1
- package/dist/{chunk-UHM3CCCC.js → chunk-WDEGGHTB.js} +12 -12
- package/dist/{chunk-UHM3CCCC.js.map → chunk-WDEGGHTB.js.map} +1 -1
- package/dist/{chunk-PIPERXI7.mjs → chunk-WV5YQL3W.mjs} +3 -3
- package/dist/{chunk-PIPERXI7.mjs.map → chunk-WV5YQL3W.mjs.map} +1 -1
- package/dist/{chunk-3EY5E35M.mjs → chunk-X3MY4PL7.mjs} +210 -107
- package/dist/chunk-X3MY4PL7.mjs.map +1 -0
- package/dist/{chunk-4NVMAYMQ.mjs → chunk-XKXZ4OW4.mjs} +4 -4
- package/dist/{chunk-4NVMAYMQ.mjs.map → chunk-XKXZ4OW4.mjs.map} +1 -1
- package/dist/{chunk-WINDP776.mjs → chunk-XSBKR3VF.mjs} +5 -5
- package/dist/{chunk-WINDP776.mjs.map → chunk-XSBKR3VF.mjs.map} +1 -1
- package/dist/{chunk-SEP5D4MI.js → chunk-Y2CP5J6Q.js} +2 -2
- package/dist/{chunk-SEP5D4MI.js.map → chunk-Y2CP5J6Q.js.map} +1 -1
- package/dist/{chunk-VVBMWV4O.js → chunk-YGDQVZEL.js} +7 -7
- package/dist/{chunk-VVBMWV4O.js.map → chunk-YGDQVZEL.js.map} +1 -1
- package/dist/{chunk-KV7XXLJV.mjs → chunk-YPGUV6BT.mjs} +3 -3
- package/dist/{chunk-KV7XXLJV.mjs.map → chunk-YPGUV6BT.mjs.map} +1 -1
- package/dist/chunk-YRCR7WY3.js +65 -0
- package/dist/chunk-YRCR7WY3.js.map +1 -0
- package/dist/{chunk-F2KW4OJF.mjs → chunk-YWLBQ3L5.mjs} +4 -4
- package/dist/{chunk-F2KW4OJF.mjs.map → chunk-YWLBQ3L5.mjs.map} +1 -1
- package/dist/{chunk-CNTDV7HM.js → chunk-YYEBFQGS.js} +4 -4
- package/dist/{chunk-CNTDV7HM.js.map → chunk-YYEBFQGS.js.map} +1 -1
- package/dist/chunk-ZL35JCDA.mjs +61 -0
- package/dist/chunk-ZL35JCDA.mjs.map +1 -0
- package/dist/chunk-ZR43XFUM.js +60 -0
- package/dist/chunk-ZR43XFUM.js.map +1 -0
- package/dist/{chunk-25F2EXOC.mjs → chunk-ZR4TYCQW.mjs} +3 -3
- package/dist/{chunk-25F2EXOC.mjs.map → chunk-ZR4TYCQW.mjs.map} +1 -1
- package/dist/cli/commands/setup.d.ts +2 -2
- package/dist/cli/commands/sync.d.ts +2 -2
- package/dist/cli/commands/sync.d.ts.map +1 -1
- package/dist/cli/index.js +76 -76
- package/dist/cli/index.mjs +29 -29
- package/dist/client/index.js +5 -5
- package/dist/client/index.mjs +4 -4
- package/dist/commerce/index.js +57 -57
- package/dist/commerce/index.mjs +5 -5
- package/dist/discover-LPN55L5B.js +21 -0
- package/dist/{discover-NGEPXZOD.js.map → discover-LPN55L5B.js.map} +1 -1
- package/dist/discover-QGA6QFSR.mjs +4 -0
- package/dist/{discover-CYXYKHZ2.mjs.map → discover-QGA6QFSR.mjs.map} +1 -1
- package/dist/engage/index.js +13 -13
- package/dist/engage/index.mjs +7 -7
- package/dist/fleet/contract.d.ts +1 -1
- package/dist/fleet/contract.js +12 -12
- package/dist/fleet/contract.mjs +1 -1
- package/dist/fleet/index.js +20 -20
- package/dist/fleet/index.mjs +5 -5
- package/dist/forms/index.js +16 -16
- package/dist/forms/index.mjs +9 -9
- package/dist/forms/server.js +10 -10
- package/dist/forms/server.mjs +8 -8
- package/dist/forms/static.js +7 -7
- package/dist/forms/static.mjs +6 -6
- package/dist/images/index.js +11 -11
- package/dist/images/index.mjs +4 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +70 -70
- package/dist/index.mjs +12 -12
- package/dist/layout/client.js +10 -10
- package/dist/layout/client.mjs +9 -9
- package/dist/layout/index.js +12 -12
- package/dist/layout/index.mjs +10 -10
- package/dist/llms/SpeakableSchema.d.ts +1 -1
- package/dist/llms/SpeakableSchema.d.ts.map +1 -1
- package/dist/llms/client.js +15 -15
- package/dist/llms/client.mjs +1 -1
- package/dist/llms/generateLLMsTxt.d.ts.map +1 -1
- package/dist/llms/index.js +32 -31
- package/dist/llms/index.js.map +1 -1
- package/dist/llms/index.mjs +8 -7
- package/dist/llms/index.mjs.map +1 -1
- package/dist/llms/types.d.ts +7 -0
- package/dist/llms/types.d.ts.map +1 -1
- package/dist/maps/index.js +5 -5
- package/dist/maps/index.mjs +3 -3
- package/dist/og/index.js +7 -7
- package/dist/og/index.mjs +2 -2
- package/dist/og/route.d.ts +6 -6
- package/dist/og/route.js.map +1 -1
- package/dist/og/route.mjs.map +1 -1
- package/dist/og/wiring.d.ts +4 -4
- package/dist/og/wiring.d.ts.map +1 -1
- package/dist/proxy/index.js +5 -5
- package/dist/proxy/index.mjs +3 -3
- package/dist/redirects/index.js +10 -10
- package/dist/redirects/index.mjs +3 -3
- package/dist/redirects/not-found.js +7 -7
- package/dist/redirects/not-found.mjs +3 -3
- package/dist/robots/indexnow.d.ts +17 -0
- package/dist/robots/indexnow.d.ts.map +1 -0
- package/dist/robots/indexnow.js +39 -0
- package/dist/robots/indexnow.js.map +1 -0
- package/dist/robots/indexnow.mjs +34 -0
- package/dist/robots/indexnow.mjs.map +1 -0
- package/dist/search/contract.d.ts +1 -1
- package/dist/search/contract.js +3 -3
- package/dist/search/contract.mjs +1 -1
- package/dist/search/index.js +7 -7
- package/dist/search/index.mjs +5 -5
- package/dist/seo/client.js +6 -6
- package/dist/seo/client.mjs +5 -5
- package/dist/seo/index.js +47 -47
- package/dist/seo/index.js.map +1 -1
- package/dist/seo/index.mjs +12 -12
- package/dist/seo/index.mjs.map +1 -1
- package/dist/seo/register-sitemap-cli.js +12 -12
- package/dist/seo/register-sitemap-cli.mjs +5 -5
- package/dist/seo/server.js +21 -21
- package/dist/seo/server.mjs +5 -5
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +2 -2
- package/dist/{server-api-A2EFCTZX.mjs → server-api-7F5P6CSH.mjs} +6 -6
- package/dist/{server-api-A2EFCTZX.mjs.map → server-api-7F5P6CSH.mjs.map} +1 -1
- package/dist/{server-api-IWTZ7C3O.js → server-api-OC5JNK6R.js} +24 -24
- package/dist/{server-api-IWTZ7C3O.js.map → server-api-OC5JNK6R.js.map} +1 -1
- package/dist/shared/build-entries.d.ts +9 -7
- package/dist/shared/build-entries.d.ts.map +1 -1
- 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/discover.d.ts +1 -1
- package/dist/sitemap/index.d.ts +7 -1
- package/dist/sitemap/index.d.ts.map +1 -1
- package/dist/sitemap/index.js +47 -30
- package/dist/sitemap/index.js.map +1 -1
- package/dist/sitemap/index.mjs +26 -9
- package/dist/sitemap/index.mjs.map +1 -1
- package/dist/sitemap/shared.d.ts +1 -1
- package/dist/sitemap/shared.d.ts.map +1 -1
- package/dist/sites/site-param.d.ts +2 -2
- package/dist/sync/index.js +17 -17
- package/dist/sync/index.mjs +4 -4
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/writeLLMsTxt-ATADOUVZ.js +20 -0
- package/dist/writeLLMsTxt-QJFXBHZF.mjs +11 -0
- package/package.json +20 -14
- package/dist/BlogPostFaqSection-P3GXVQPA.mjs +0 -11
- package/dist/BlogPostFaqSection-P3GXVQPA.mjs.map +0 -1
- package/dist/BlogPostFaqSection-YG5J4B4U.js +0 -13
- package/dist/BlogPostFaqSection-YG5J4B4U.js.map +0 -1
- package/dist/BlogViewTracker-Q7Y4MN4B.mjs.map +0 -1
- package/dist/BlogViewTracker-S3CSRBIZ.js.map +0 -1
- package/dist/ChatWidget-76R7NOWD.js +0 -24
- package/dist/ChatWidget-G3KOUDX5.mjs +0 -15
- package/dist/EngageWidget-HD4F2ZVE.js +0 -21
- package/dist/EngageWidget-UJ47PQFG.mjs +0 -12
- package/dist/ManagedForm-4ZGU4R3G.js +0 -25
- package/dist/ManagedForm-Z7QE4KYI.mjs +0 -16
- package/dist/ManagedNewsletterForm-22EPZB7Z.js.map +0 -1
- package/dist/ManagedNewsletterForm-4TVO2VL4.mjs.map +0 -1
- package/dist/SitemapSync-GF4OKN4Z.js +0 -18
- package/dist/SitemapSync-WIUEYOBS.mjs +0 -9
- package/dist/blog/AuthorCard.d.ts.map +0 -1
- package/dist/blog/AuthorPage.d.ts.map +0 -1
- package/dist/blog/BlogFAQ.d.ts.map +0 -1
- package/dist/blog/BlogLayout.d.ts.map +0 -1
- package/dist/blog/BlogList.d.ts.map +0 -1
- package/dist/blog/BlogPost.d.ts.map +0 -1
- package/dist/blog/BlogPostFaqSection.d.ts +0 -3
- package/dist/blog/BlogPostFaqSection.d.ts.map +0 -1
- package/dist/blog/BlogSidebar.d.ts.map +0 -1
- package/dist/blog/BlogViewTracker.d.ts.map +0 -1
- package/dist/blog/ClusterLandingPage.d.ts.map +0 -1
- package/dist/blog/ClusterNavigation.d.ts.map +0 -1
- package/dist/blog/ManagedNewsletterForm.d.ts.map +0 -1
- package/dist/blog/NewsletterShell.d.ts.map +0 -1
- package/dist/blog/NewsletterWidget.d.ts.map +0 -1
- package/dist/blog/RelatedPosts.d.ts.map +0 -1
- package/dist/blog/ServiceCallout.d.ts.map +0 -1
- package/dist/blog/TableOfContents.d.ts.map +0 -1
- package/dist/blog/artwork.d.ts +0 -14
- package/dist/blog/artwork.d.ts.map +0 -1
- package/dist/blog/author-schema.d.ts.map +0 -1
- package/dist/blog/author-social.d.ts.map +0 -1
- package/dist/blog/blog-styles.d.ts.map +0 -1
- package/dist/blog/blog-tables.d.ts.map +0 -1
- package/dist/blog/excerpt.d.ts.map +0 -1
- package/dist/blog/index.d.ts.map +0 -1
- package/dist/blog/index.js.map +0 -1
- package/dist/blog/index.mjs.map +0 -1
- package/dist/blog/processBlogHtml.d.ts.map +0 -1
- package/dist/blog/public-reads.d.ts +0 -27
- package/dist/blog/public-reads.d.ts.map +0 -1
- package/dist/blog/reading-time.d.ts.map +0 -1
- package/dist/blog/routes.d.ts.map +0 -1
- package/dist/blog/server-core.d.ts.map +0 -1
- package/dist/blog/server-ui.d.ts +0 -29
- package/dist/blog/server-ui.d.ts.map +0 -1
- package/dist/blog/server-ui.js +0 -267
- package/dist/blog/server-ui.js.map +0 -1
- package/dist/blog/server-ui.mjs +0 -203
- package/dist/blog/server-ui.mjs.map +0 -1
- package/dist/blog/server.d.ts.map +0 -1
- package/dist/blog/server.js +0 -207
- package/dist/blog/server.mjs +0 -10
- package/dist/blog/types.d.ts.map +0 -1
- package/dist/blog/widget-styles.d.ts +0 -9
- package/dist/blog/widget-styles.d.ts.map +0 -1
- package/dist/blog-styles-6EY3BS5Y.js.map +0 -1
- package/dist/blog-styles-7AKRUMV7.mjs.map +0 -1
- package/dist/blog-tables-DWDEWA3S.js +0 -17
- package/dist/blog-tables-DWDEWA3S.js.map +0 -1
- package/dist/blog-tables-QGIHOESZ.mjs +0 -4
- package/dist/blog-tables-QGIHOESZ.mjs.map +0 -1
- package/dist/chunk-3EY5E35M.mjs.map +0 -1
- package/dist/chunk-4LYW6PMU.mjs.map +0 -1
- package/dist/chunk-4TLFZ6NR.mjs.map +0 -1
- package/dist/chunk-5B4J3IBX.mjs +0 -164
- package/dist/chunk-5B4J3IBX.mjs.map +0 -1
- package/dist/chunk-5FETEH3Y.mjs +0 -6
- package/dist/chunk-75A4KY4X.mjs.map +0 -1
- package/dist/chunk-7R6H3GQ4.js +0 -8
- package/dist/chunk-7TC3GRXQ.js.map +0 -1
- package/dist/chunk-AJBOEV4I.mjs.map +0 -1
- package/dist/chunk-AK6JZ3OE.mjs.map +0 -1
- package/dist/chunk-FHPW7XVH.js +0 -175
- package/dist/chunk-FHPW7XVH.js.map +0 -1
- package/dist/chunk-G2WC3CXW.js.map +0 -1
- package/dist/chunk-GHECYDSZ.mjs.map +0 -1
- package/dist/chunk-GQF6QAQS.js.map +0 -1
- package/dist/chunk-H5NBFNND.js.map +0 -1
- package/dist/chunk-HX6ZG3PP.js.map +0 -1
- package/dist/chunk-JLVCWC6A.mjs.map +0 -1
- package/dist/chunk-K4TCEAUX.js.map +0 -1
- package/dist/chunk-LMEUYRRR.js.map +0 -1
- package/dist/chunk-MWKI6ZIS.mjs.map +0 -1
- package/dist/chunk-O237KKCX.mjs.map +0 -1
- package/dist/chunk-Q2NBD54V.js.map +0 -1
- package/dist/chunk-R7GHP3FY.js.map +0 -1
- package/dist/chunk-W2U5SLMY.js.map +0 -1
- package/dist/chunk-XEB77DQS.js.map +0 -1
- package/dist/chunk-Y5QZPUL3.mjs.map +0 -1
- package/dist/chunk-YFX3Q56O.mjs.map +0 -1
- package/dist/chunk-YN7QHLWF.js.map +0 -1
- package/dist/chunk-YQTE32QK.js.map +0 -1
- package/dist/chunk-ZPLN537Q.mjs.map +0 -1
- package/dist/chunk-ZVI7MBVF.mjs.map +0 -1
- package/dist/discover-CYXYKHZ2.mjs +0 -4
- package/dist/discover-NGEPXZOD.js +0 -21
- package/dist/writeLLMsTxt-CQ7A4AAL.js +0 -19
- package/dist/writeLLMsTxt-NX5MSPYX.mjs +0 -10
- /package/dist/{blog → articles}/server.js.map +0 -0
- /package/dist/{blog → articles}/server.mjs.map +0 -0
package/dist/og/route.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/og/route-fit.ts","../../src/og/route.tsx"],"names":["OG_WIDTH","OG_HEIGHT","TITLE_MAX_PX","TITLE_MIN_PX","jsxs","jsx","barTextColor"],"mappings":"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","file":"route.js","sourcesContent":["/**\n * @sonordev/site-kit/og — title fitting for the runtime (Satori) card.\n *\n * The build-time card is fitted in the page: Chrome measures the rendered\n * title and steps it down until it fits (template.ts). The runtime card is\n * Satori, which has no DOM to measure, so its title sat at a fixed 84px,\n * uppercase, with no fitting. Beside the 430px photo that holds about 11\n * characters a line, so a real post title ran off the bottom of the card.\n * watsonhac.com, reinhart and vsf-consulting-nextjs each worked around it by\n * dropping the photo past a hand-picked length (40 or 50 characters).\n *\n * This does what the build-time fitter does, from an estimate instead of a\n * measurement: the same size range (TITLE_MAX_PX down to TITLE_MIN_PX, in 2px\n * steps), word-wrapped at an estimated glyph width against the real column\n * width and the height the kicker, subtitle and bar leave. When the title\n * can't fit beside the photo even at the floor, the photo goes and the title\n * gets the full width.\n *\n * The estimate is deliberately wide (uppercase, bold-ish) so an unusual\n * face errs toward a smaller title, never an overflowing one, and the title\n * box clips at its height as the backstop.\n *\n * Pure: no DOM, no Node built-ins. Geometry constants are the card's own\n * (route.tsx imports them), so the two can't drift.\n */\n\nimport { OG_HEIGHT, OG_WIDTH, TITLE_MAX_PX, TITLE_MIN_PX } from './config'\n\n/** The runtime card's layout, in px. route.tsx renders exactly this. */\nexport const ROUTE_CARD = {\n padX: 68,\n gap: 48,\n photoWidth: 430,\n photoHeight: 470,\n barHeight: 74,\n /** Breathing room above and below the copy column. */\n padY: 44,\n kickerPx: 24,\n kickerGap: 18,\n subtitlePx: 30,\n subtitleGap: 20,\n subtitleLineHeight: 1.3,\n titleLineHeight: 1.02,\n} as const\n\n/**\n * Average advance of an UPPERCASE glyph, in em. Calibrated against next/og's\n * bundled face and a bold sans; errs wide. Narrow glyphs are counted\n * separately so \"ILLINOIS\" isn't treated like \"MMMMMMMM\".\n */\nconst UPPER_EM = 0.68\nconst NARROW_EM = 0.32\nconst SPACE_EM = 0.3\n/** Mixed-case body text (the subtitle), in em. */\nconst BODY_EM = 0.52\n/** The kicker is uppercase with 6px letter spacing at 24px. */\nconst KICKER_EM = 0.72 + 6 / 24\n\nconst NARROW = /[IJ1.,:;'’!|()\\-]/\n\n/** Estimated width of `text` set uppercase at `px`. */\nexport function uppercaseWidth(text: string, px: number): number {\n let em = 0\n for (const ch of text.toUpperCase()) {\n em += ch === ' ' ? SPACE_EM : NARROW.test(ch) ? NARROW_EM : UPPER_EM\n }\n return em * px\n}\n\n/**\n * Lines `text` wraps to in a column `width` px wide, greedy by word, the way\n * the browser and Satori wrap. `\\n` is a hard break. Returns Infinity when a\n * single word is wider than the column: it can't wrap, so no line count fits.\n */\nexport function wrappedLines(\n text: string,\n width: number,\n measure: (s: string) => number,\n): number {\n let lines = 0\n for (const paragraph of text.split('\\n')) {\n const words = paragraph.split(/\\s+/).filter(Boolean)\n if (!words.length) {\n lines++\n continue\n }\n let current = ''\n for (const word of words) {\n if (measure(word) > width) return Infinity\n const next = current ? `${current} ${word}` : word\n if (current && measure(next) > width) {\n lines++\n current = word\n } else {\n current = next\n }\n }\n lines++\n }\n return lines\n}\n\nexport interface RouteFitInput {\n title: string\n kicker?: string\n subtitle?: string\n /** A photo was offered. */\n photo: boolean\n /** The card has a bottom bar. */\n bar: boolean\n}\n\nexport interface RouteFit {\n /** Title size in px. */\n titlePx: number\n /** Keep the photo. False when the title only fits at full width. */\n photo: boolean\n /** Height the title box may use; route.tsx clips the title to it. */\n titleMaxHeight: number\n /** The title fits at `titlePx` by the estimate (false only at the floor, full width). */\n fits: boolean\n}\n\nfunction columnWidth(photo: boolean): number {\n const inner = OG_WIDTH - ROUTE_CARD.padX * 2\n return photo ? inner - ROUTE_CARD.photoWidth - ROUTE_CARD.gap : inner\n}\n\n/** Height left for the title once the kicker and subtitle have theirs. */\nfunction titleBudget(input: RouteFitInput, width: number): number {\n const c = ROUTE_CARD\n let height = OG_HEIGHT - (input.bar ? c.barHeight : 0) - c.padY * 2\n if (input.kicker) {\n const lines = wrappedLines(input.kicker, width, s => s.length * KICKER_EM * c.kickerPx)\n height -= Math.min(lines, 2) * c.kickerPx * 1.2 + c.kickerGap\n }\n if (input.subtitle) {\n const lines = wrappedLines(input.subtitle, width, s => s.length * BODY_EM * c.subtitlePx)\n height -= Math.min(lines, 3) * c.subtitlePx * c.subtitleLineHeight + c.subtitleGap\n }\n return height\n}\n\n/** Largest title size that fits `width`, or null when none down to the floor does. */\nfunction largestFit(input: RouteFitInput, photo: boolean): { px: number; budget: number } | null {\n const width = columnWidth(photo)\n const budget = titleBudget(input, width)\n for (let px = TITLE_MAX_PX; px >= TITLE_MIN_PX; px -= 2) {\n const lines = wrappedLines(input.title, width, s => uppercaseWidth(s, px))\n if (lines * px * ROUTE_CARD.titleLineHeight <= budget) return { px, budget }\n }\n return null\n}\n\n/**\n * Size the title, and decide whether the photo stays.\n *\n * Beside the photo first, since the photo is the page's own image and worth\n * keeping. If even the floor size doesn't fit there, drop the photo and fit\n * the full width. If that fails too, the title sits at the floor and clips.\n */\nexport function fitRouteTitle(input: RouteFitInput): RouteFit {\n if (input.photo) {\n const withPhoto = largestFit(input, true)\n if (withPhoto) {\n return { titlePx: withPhoto.px, photo: true, titleMaxHeight: withPhoto.budget, fits: true }\n }\n }\n const full = largestFit(input, false)\n if (full) return { titlePx: full.px, photo: false, titleMaxHeight: full.budget, fits: true }\n return {\n titlePx: TITLE_MIN_PX,\n photo: false,\n titleMaxHeight: titleBudget(input, columnWidth(false)),\n fits: false,\n }\n}\n","/**\n * @sonordev/site-kit/og/route — tier 2: per-entity runtime OG cards.\n *\n * `sonor-setup og` covers the build-time cards: the site card and one per\n * route. This covers the cards that must vary per URL and can't wait for a\n * rebuild — one per blog post, per event, per product — rendered on demand\n * with next/og (Satori).\n *\n * TWO SHAPES, ONE CARD:\n *\n * `createOgImage` is the metadata file convention, and the one to reach for.\n * Next calls an `opengraph-image.tsx` default export with `{ params }` and\n * wires og:image and twitter:image for the page by itself, and `sonor-setup\n * og` sees the file and leaves the folder alone:\n *\n * ```tsx\n * // app/blog/[slug]/opengraph-image.tsx\n * import { createOgImage } from '@sonordev/site-kit/og/route'\n * import { getBlogPost } from '@sonordev/site-kit/blog/server'\n *\n * export { size, contentType } from '@sonordev/site-kit/og/route'\n * export const alt = 'From the Acme blog'\n *\n * export default createOgImage<{ slug: string }>(async ({ slug }) => {\n * const post = await getBlogPost(slug)\n * if (!post) return null // 404: a bad slug never renders a card\n * return {\n * theme: { bg: '#111110', text: '#faf6f0', accent: '#ad1d25' },\n * kicker: 'From the blog',\n * title: post.title,\n * photoUrl: post.featured_image, // absolute URL (Satori requirement)\n * bar: 'yourdomain.com',\n * }\n * })\n * ```\n *\n * The page then passes `images: false` to generateBlogPostMetadata: declared\n * images beat the file convention.\n *\n * `createOgImageRoute` is a plain route handler, `GET(request, { params })`,\n * for a card served at a URL of your choosing (`app/og/[slug]/route.tsx`).\n * Next doesn't wire it into metadata; the page declares it.\n *\n * Sites needed an adapter between the two before `createOgImage` existed\n * (watsonhac.com, reinhart, vsf-consulting-nextjs: a default export that\n * built a dummy Request to call the GET handler).\n *\n * SATORI CONSTRAINTS (why this is not the static template): flexbox-only CSS\n * subset, no external stylesheets, images must be absolute URLs or data URIs,\n * and fonts must be provided as ArrayBuffers via `fonts` — Satori cannot load\n * Google Fonts at runtime. Without fonts it falls back to Satori's default\n * face; brand-critical cards should pass font data (fetch the TTF once at\n * module init and reuse the promise).\n *\n * TITLE FITTING: Satori can't measure, so the title is sized from an estimate\n * (og/route-fit.ts) over the same range the build-time card uses, and the\n * photo is dropped when the title can't fit beside it. Long post titles no\n * longer run off the card.\n */\n\nimport React from 'react'\nimport { OG_HEIGHT, OG_WIDTH } from './config'\nimport { barTextColor } from './contrast'\nimport { fitRouteTitle, ROUTE_CARD } from './route-fit'\n\nexport interface OgRouteTheme {\n bg: string\n text: string\n accent: string\n muted?: string\n /** og.config.ts's surface color. Only used as a bar-text candidate. */\n surface?: string\n /**\n * Bottom-bar text, set on `accent`. Defaults to the first of `surface`,\n * `bg`, `text` that reaches WCAG AA for large text (3:1) against the\n * accent. It used to be `text`, which is unreadable wherever text and\n * accent are both strong (navy on crimson).\n */\n barText?: string\n}\n\nexport interface OgRouteCard {\n /** og.config.ts's `theme` can be passed as is. */\n theme: OgRouteTheme\n kicker?: string\n title: string\n subtitle?: string\n /** Absolute URL (Satori requirement) of the page's own image. */\n photoUrl?: string\n /** Bottom-bar text, e.g. the domain. */\n bar?: string\n}\n\nexport interface OgRouteFont {\n name: string\n data: ArrayBuffer\n weight?: number\n style?: 'normal' | 'italic'\n}\n\nexport interface OgImageOptions {\n fonts?: OgRouteFont[]\n}\n\n/** For `export { size, contentType } from '@sonordev/site-kit/og/route'` in an opengraph-image file. */\nexport const size = { width: OG_WIDTH, height: OG_HEIGHT }\nexport const contentType = 'image/png'\n\ntype Resolver<Ctx> = (req: Request, ctx: Ctx) => Promise<OgRouteCard | null> | OgRouteCard | null\n\n/**\n * Satori-safe element tree — exported for tests and for sites that want to\n * extend it inside their own ImageResponse.\n */\nexport function ogRouteElement(card: OgRouteCard): React.ReactElement {\n const { theme } = card\n const muted = theme.muted || theme.text\n // Only an absolute URL (or data URI) renders in Satori; anything else would\n // throw inside the render, so it's treated as no photo.\n const hasPhoto = !!card.photoUrl && /^(https?:|data:)/.test(card.photoUrl)\n const fit = fitRouteTitle({\n title: card.title,\n kicker: card.kicker,\n subtitle: card.subtitle,\n photo: hasPhoto,\n bar: !!card.bar,\n })\n const titleLine = fit.titlePx * ROUTE_CARD.titleLineHeight\n // Clip on a whole line, so an estimate that ran short loses a line cleanly\n // instead of spilling onto the bar.\n const titleMaxHeight = Math.max(1, Math.floor(fit.titleMaxHeight / titleLine)) * titleLine\n\n return (\n <div\n style={{\n width: '100%',\n height: '100%',\n display: 'flex',\n flexDirection: 'column',\n backgroundColor: theme.bg,\n color: theme.text,\n fontSize: 32,\n }}\n >\n <div\n style={{\n display: 'flex',\n flex: 1,\n alignItems: 'center',\n padding: `0 ${ROUTE_CARD.padX}px`,\n gap: ROUTE_CARD.gap,\n minHeight: 0,\n }}\n >\n <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minWidth: 0 }}>\n {card.kicker ? (\n <div\n style={{\n fontSize: ROUTE_CARD.kickerPx,\n letterSpacing: 6,\n textTransform: 'uppercase',\n color: theme.accent,\n marginBottom: ROUTE_CARD.kickerGap,\n fontWeight: 700,\n }}\n >\n {card.kicker}\n </div>\n ) : null}\n <div\n style={{\n display: 'flex',\n fontSize: fit.titlePx,\n fontWeight: 800,\n lineHeight: ROUTE_CARD.titleLineHeight,\n textTransform: 'uppercase',\n maxHeight: titleMaxHeight,\n overflow: 'hidden',\n }}\n >\n {card.title}\n </div>\n {card.subtitle ? (\n <div\n style={{\n fontSize: ROUTE_CARD.subtitlePx,\n marginTop: ROUTE_CARD.subtitleGap,\n color: muted,\n lineHeight: ROUTE_CARD.subtitleLineHeight,\n }}\n >\n {card.subtitle}\n </div>\n ) : null}\n </div>\n {hasPhoto && fit.photo ? (\n <img\n src={card.photoUrl}\n width={ROUTE_CARD.photoWidth}\n height={ROUTE_CARD.photoHeight}\n style={{ borderRadius: 24, objectFit: 'cover' }}\n />\n ) : null}\n </div>\n {card.bar ? (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n height: ROUTE_CARD.barHeight,\n backgroundColor: theme.accent,\n color: barTextColor(theme),\n fontSize: 30,\n fontWeight: 800,\n letterSpacing: 3,\n textTransform: 'uppercase',\n }}\n >\n {card.bar}\n </div>\n ) : null}\n </div>\n )\n}\n\n/**\n * Render a card, or 404 for null. Single source of truth for both factories:\n * the file convention and the route handler differ only in how they're called.\n */\nasync function renderCard(card: OgRouteCard | null, options: OgImageOptions): Promise<Response> {\n if (!card) return new Response('Not found', { status: 404 })\n\n // next/og is imported lazily so this module stays importable in test/node\n // contexts that lack the Next server runtime.\n const { ImageResponse } = await import('next/og')\n return new ImageResponse(ogRouteElement(card), {\n ...size,\n ...(options.fonts?.length ? { fonts: options.fonts as any } : {}),\n })\n}\n\n/**\n * Metadata file-convention factory, for `export default` from an\n * `opengraph-image.tsx` (or `twitter-image.tsx`). Next calls it with\n * `{ params }` (a promise on Next 15+) and, with `generateImageMetadata`, an\n * `id`. The resolver gets both already awaited. Return null for a 404.\n */\nexport function createOgImage<Params extends Record<string, string | string[]> = Record<string, string>>(\n resolve: (params: Params, ctx: { id?: string }) => Promise<OgRouteCard | null> | OgRouteCard | null,\n options: OgImageOptions = {},\n) {\n return async function Image(props: {\n params: Params | Promise<Params>\n id?: string | Promise<string>\n }): Promise<Response> {\n const params = await props.params\n const id = props.id === undefined ? undefined : await props.id\n return renderCard(await resolve(params, { id }), options)\n }\n}\n\n/**\n * Route handler factory, `export const GET` from a `route.tsx`. Returns 404\n * when the resolver returns null, so a bad slug never renders a broken card.\n *\n * Next doesn't wire a route handler into metadata. For a card Next should\n * attach to the page by itself, use `createOgImage` in `opengraph-image.tsx`.\n */\nexport function createOgImageRoute<Ctx = { params: Promise<Record<string, string>> }>(\n resolve: Resolver<Ctx>,\n options: OgImageOptions = {},\n) {\n return async function GET(req: Request, ctx: Ctx): Promise<Response> {\n return renderCard(await resolve(req, ctx), options)\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/og/route-fit.ts","../../src/og/route.tsx"],"names":["OG_WIDTH","OG_HEIGHT","TITLE_MAX_PX","TITLE_MIN_PX","jsxs","jsx","barTextColor"],"mappings":"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","file":"route.js","sourcesContent":["/**\n * @sonordev/site-kit/og — title fitting for the runtime (Satori) card.\n *\n * The build-time card is fitted in the page: Chrome measures the rendered\n * title and steps it down until it fits (template.ts). The runtime card is\n * Satori, which has no DOM to measure, so its title sat at a fixed 84px,\n * uppercase, with no fitting. Beside the 430px photo that holds about 11\n * characters a line, so a real post title ran off the bottom of the card.\n * watsonhac.com, reinhart and vsf-consulting-nextjs each worked around it by\n * dropping the photo past a hand-picked length (40 or 50 characters).\n *\n * This does what the build-time fitter does, from an estimate instead of a\n * measurement: the same size range (TITLE_MAX_PX down to TITLE_MIN_PX, in 2px\n * steps), word-wrapped at an estimated glyph width against the real column\n * width and the height the kicker, subtitle and bar leave. When the title\n * can't fit beside the photo even at the floor, the photo goes and the title\n * gets the full width.\n *\n * The estimate is deliberately wide (uppercase, bold-ish) so an unusual\n * face errs toward a smaller title, never an overflowing one, and the title\n * box clips at its height as the backstop.\n *\n * Pure: no DOM, no Node built-ins. Geometry constants are the card's own\n * (route.tsx imports them), so the two can't drift.\n */\n\nimport { OG_HEIGHT, OG_WIDTH, TITLE_MAX_PX, TITLE_MIN_PX } from './config'\n\n/** The runtime card's layout, in px. route.tsx renders exactly this. */\nexport const ROUTE_CARD = {\n padX: 68,\n gap: 48,\n photoWidth: 430,\n photoHeight: 470,\n barHeight: 74,\n /** Breathing room above and below the copy column. */\n padY: 44,\n kickerPx: 24,\n kickerGap: 18,\n subtitlePx: 30,\n subtitleGap: 20,\n subtitleLineHeight: 1.3,\n titleLineHeight: 1.02,\n} as const\n\n/**\n * Average advance of an UPPERCASE glyph, in em. Calibrated against next/og's\n * bundled face and a bold sans; errs wide. Narrow glyphs are counted\n * separately so \"ILLINOIS\" isn't treated like \"MMMMMMMM\".\n */\nconst UPPER_EM = 0.68\nconst NARROW_EM = 0.32\nconst SPACE_EM = 0.3\n/** Mixed-case body text (the subtitle), in em. */\nconst BODY_EM = 0.52\n/** The kicker is uppercase with 6px letter spacing at 24px. */\nconst KICKER_EM = 0.72 + 6 / 24\n\nconst NARROW = /[IJ1.,:;'’!|()\\-]/\n\n/** Estimated width of `text` set uppercase at `px`. */\nexport function uppercaseWidth(text: string, px: number): number {\n let em = 0\n for (const ch of text.toUpperCase()) {\n em += ch === ' ' ? SPACE_EM : NARROW.test(ch) ? NARROW_EM : UPPER_EM\n }\n return em * px\n}\n\n/**\n * Lines `text` wraps to in a column `width` px wide, greedy by word, the way\n * the browser and Satori wrap. `\\n` is a hard break. Returns Infinity when a\n * single word is wider than the column: it can't wrap, so no line count fits.\n */\nexport function wrappedLines(\n text: string,\n width: number,\n measure: (s: string) => number,\n): number {\n let lines = 0\n for (const paragraph of text.split('\\n')) {\n const words = paragraph.split(/\\s+/).filter(Boolean)\n if (!words.length) {\n lines++\n continue\n }\n let current = ''\n for (const word of words) {\n if (measure(word) > width) return Infinity\n const next = current ? `${current} ${word}` : word\n if (current && measure(next) > width) {\n lines++\n current = word\n } else {\n current = next\n }\n }\n lines++\n }\n return lines\n}\n\nexport interface RouteFitInput {\n title: string\n kicker?: string\n subtitle?: string\n /** A photo was offered. */\n photo: boolean\n /** The card has a bottom bar. */\n bar: boolean\n}\n\nexport interface RouteFit {\n /** Title size in px. */\n titlePx: number\n /** Keep the photo. False when the title only fits at full width. */\n photo: boolean\n /** Height the title box may use; route.tsx clips the title to it. */\n titleMaxHeight: number\n /** The title fits at `titlePx` by the estimate (false only at the floor, full width). */\n fits: boolean\n}\n\nfunction columnWidth(photo: boolean): number {\n const inner = OG_WIDTH - ROUTE_CARD.padX * 2\n return photo ? inner - ROUTE_CARD.photoWidth - ROUTE_CARD.gap : inner\n}\n\n/** Height left for the title once the kicker and subtitle have theirs. */\nfunction titleBudget(input: RouteFitInput, width: number): number {\n const c = ROUTE_CARD\n let height = OG_HEIGHT - (input.bar ? c.barHeight : 0) - c.padY * 2\n if (input.kicker) {\n const lines = wrappedLines(input.kicker, width, s => s.length * KICKER_EM * c.kickerPx)\n height -= Math.min(lines, 2) * c.kickerPx * 1.2 + c.kickerGap\n }\n if (input.subtitle) {\n const lines = wrappedLines(input.subtitle, width, s => s.length * BODY_EM * c.subtitlePx)\n height -= Math.min(lines, 3) * c.subtitlePx * c.subtitleLineHeight + c.subtitleGap\n }\n return height\n}\n\n/** Largest title size that fits `width`, or null when none down to the floor does. */\nfunction largestFit(input: RouteFitInput, photo: boolean): { px: number; budget: number } | null {\n const width = columnWidth(photo)\n const budget = titleBudget(input, width)\n for (let px = TITLE_MAX_PX; px >= TITLE_MIN_PX; px -= 2) {\n const lines = wrappedLines(input.title, width, s => uppercaseWidth(s, px))\n if (lines * px * ROUTE_CARD.titleLineHeight <= budget) return { px, budget }\n }\n return null\n}\n\n/**\n * Size the title, and decide whether the photo stays.\n *\n * Beside the photo first, since the photo is the page's own image and worth\n * keeping. If even the floor size doesn't fit there, drop the photo and fit\n * the full width. If that fails too, the title sits at the floor and clips.\n */\nexport function fitRouteTitle(input: RouteFitInput): RouteFit {\n if (input.photo) {\n const withPhoto = largestFit(input, true)\n if (withPhoto) {\n return { titlePx: withPhoto.px, photo: true, titleMaxHeight: withPhoto.budget, fits: true }\n }\n }\n const full = largestFit(input, false)\n if (full) return { titlePx: full.px, photo: false, titleMaxHeight: full.budget, fits: true }\n return {\n titlePx: TITLE_MIN_PX,\n photo: false,\n titleMaxHeight: titleBudget(input, columnWidth(false)),\n fits: false,\n }\n}\n","/**\n * @sonordev/site-kit/og/route — tier 2: per-entity runtime OG cards.\n *\n * `sonor-setup og` covers the build-time cards: the site card and one per\n * route. This covers the cards that must vary per URL and can't wait for a\n * rebuild — one per article, per event, per product — rendered on demand\n * with next/og (Satori).\n *\n * TWO SHAPES, ONE CARD:\n *\n * `createOgImage` is the metadata file convention, and the one to reach for.\n * Next calls an `opengraph-image.tsx` default export with `{ params }` and\n * wires og:image and twitter:image for the page by itself, and `sonor-setup\n * og` sees the file and leaves the folder alone:\n *\n * ```tsx\n * // app/blog/[slug]/opengraph-image.tsx\n * import { createOgImage } from '@sonordev/site-kit/og/route'\n * import { getArticle } from '@sonordev/site-kit/articles/server'\n *\n * export { size, contentType } from '@sonordev/site-kit/og/route'\n * export const alt = 'From the Acme article'\n *\n * export default createOgImage<{ slug: string }>(async ({ slug }) => {\n * const post = await getArticle(slug)\n * if (!post) return null // 404: a bad slug never renders a card\n * return {\n * theme: { bg: '#111110', text: '#faf6f0', accent: '#ad1d25' },\n * kicker: 'From the publication',\n * title: post.title,\n * photoUrl: post.featured_image, // absolute URL (Satori requirement)\n * bar: 'yourdomain.com',\n * }\n * })\n * ```\n *\n * The page then passes `images: false` to generateArticleMetadata: declared\n * images beat the file convention.\n *\n * `createOgImageRoute` is a plain route handler, `GET(request, { params })`,\n * for a card served at a URL of your choosing (`app/og/[slug]/route.tsx`).\n * Next doesn't wire it into metadata; the page declares it.\n *\n * Sites needed an adapter between the two before `createOgImage` existed\n * (watsonhac.com, reinhart, vsf-consulting-nextjs: a default export that\n * built a dummy Request to call the GET handler).\n *\n * SATORI CONSTRAINTS (why this is not the static template): flexbox-only CSS\n * subset, no external stylesheets, images must be absolute URLs or data URIs,\n * and fonts must be provided as ArrayBuffers via `fonts` — Satori cannot load\n * Google Fonts at runtime. Without fonts it falls back to Satori's default\n * face; brand-critical cards should pass font data (fetch the TTF once at\n * module init and reuse the promise).\n *\n * TITLE FITTING: Satori can't measure, so the title is sized from an estimate\n * (og/route-fit.ts) over the same range the build-time card uses, and the\n * photo is dropped when the title can't fit beside it. Long post titles no\n * longer run off the card.\n */\n\nimport React from 'react'\nimport { OG_HEIGHT, OG_WIDTH } from './config'\nimport { barTextColor } from './contrast'\nimport { fitRouteTitle, ROUTE_CARD } from './route-fit'\n\nexport interface OgRouteTheme {\n bg: string\n text: string\n accent: string\n muted?: string\n /** og.config.ts's surface color. Only used as a bar-text candidate. */\n surface?: string\n /**\n * Bottom-bar text, set on `accent`. Defaults to the first of `surface`,\n * `bg`, `text` that reaches WCAG AA for large text (3:1) against the\n * accent. It used to be `text`, which is unreadable wherever text and\n * accent are both strong (navy on crimson).\n */\n barText?: string\n}\n\nexport interface OgRouteCard {\n /** og.config.ts's `theme` can be passed as is. */\n theme: OgRouteTheme\n kicker?: string\n title: string\n subtitle?: string\n /** Absolute URL (Satori requirement) of the page's own image. */\n photoUrl?: string\n /** Bottom-bar text, e.g. the domain. */\n bar?: string\n}\n\nexport interface OgRouteFont {\n name: string\n data: ArrayBuffer\n weight?: number\n style?: 'normal' | 'italic'\n}\n\nexport interface OgImageOptions {\n fonts?: OgRouteFont[]\n}\n\n/** For `export { size, contentType } from '@sonordev/site-kit/og/route'` in an opengraph-image file. */\nexport const size = { width: OG_WIDTH, height: OG_HEIGHT }\nexport const contentType = 'image/png'\n\ntype Resolver<Ctx> = (req: Request, ctx: Ctx) => Promise<OgRouteCard | null> | OgRouteCard | null\n\n/**\n * Satori-safe element tree — exported for tests and for sites that want to\n * extend it inside their own ImageResponse.\n */\nexport function ogRouteElement(card: OgRouteCard): React.ReactElement {\n const { theme } = card\n const muted = theme.muted || theme.text\n // Only an absolute URL (or data URI) renders in Satori; anything else would\n // throw inside the render, so it's treated as no photo.\n const hasPhoto = !!card.photoUrl && /^(https?:|data:)/.test(card.photoUrl)\n const fit = fitRouteTitle({\n title: card.title,\n kicker: card.kicker,\n subtitle: card.subtitle,\n photo: hasPhoto,\n bar: !!card.bar,\n })\n const titleLine = fit.titlePx * ROUTE_CARD.titleLineHeight\n // Clip on a whole line, so an estimate that ran short loses a line cleanly\n // instead of spilling onto the bar.\n const titleMaxHeight = Math.max(1, Math.floor(fit.titleMaxHeight / titleLine)) * titleLine\n\n return (\n <div\n style={{\n width: '100%',\n height: '100%',\n display: 'flex',\n flexDirection: 'column',\n backgroundColor: theme.bg,\n color: theme.text,\n fontSize: 32,\n }}\n >\n <div\n style={{\n display: 'flex',\n flex: 1,\n alignItems: 'center',\n padding: `0 ${ROUTE_CARD.padX}px`,\n gap: ROUTE_CARD.gap,\n minHeight: 0,\n }}\n >\n <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minWidth: 0 }}>\n {card.kicker ? (\n <div\n style={{\n fontSize: ROUTE_CARD.kickerPx,\n letterSpacing: 6,\n textTransform: 'uppercase',\n color: theme.accent,\n marginBottom: ROUTE_CARD.kickerGap,\n fontWeight: 700,\n }}\n >\n {card.kicker}\n </div>\n ) : null}\n <div\n style={{\n display: 'flex',\n fontSize: fit.titlePx,\n fontWeight: 800,\n lineHeight: ROUTE_CARD.titleLineHeight,\n textTransform: 'uppercase',\n maxHeight: titleMaxHeight,\n overflow: 'hidden',\n }}\n >\n {card.title}\n </div>\n {card.subtitle ? (\n <div\n style={{\n fontSize: ROUTE_CARD.subtitlePx,\n marginTop: ROUTE_CARD.subtitleGap,\n color: muted,\n lineHeight: ROUTE_CARD.subtitleLineHeight,\n }}\n >\n {card.subtitle}\n </div>\n ) : null}\n </div>\n {hasPhoto && fit.photo ? (\n <img\n src={card.photoUrl}\n width={ROUTE_CARD.photoWidth}\n height={ROUTE_CARD.photoHeight}\n style={{ borderRadius: 24, objectFit: 'cover' }}\n />\n ) : null}\n </div>\n {card.bar ? (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n height: ROUTE_CARD.barHeight,\n backgroundColor: theme.accent,\n color: barTextColor(theme),\n fontSize: 30,\n fontWeight: 800,\n letterSpacing: 3,\n textTransform: 'uppercase',\n }}\n >\n {card.bar}\n </div>\n ) : null}\n </div>\n )\n}\n\n/**\n * Render a card, or 404 for null. Single source of truth for both factories:\n * the file convention and the route handler differ only in how they're called.\n */\nasync function renderCard(card: OgRouteCard | null, options: OgImageOptions): Promise<Response> {\n if (!card) return new Response('Not found', { status: 404 })\n\n // next/og is imported lazily so this module stays importable in test/node\n // contexts that lack the Next server runtime.\n const { ImageResponse } = await import('next/og')\n return new ImageResponse(ogRouteElement(card), {\n ...size,\n ...(options.fonts?.length ? { fonts: options.fonts as any } : {}),\n })\n}\n\n/**\n * Metadata file-convention factory, for `export default` from an\n * `opengraph-image.tsx` (or `twitter-image.tsx`). Next calls it with\n * `{ params }` (a promise on Next 15+) and, with `generateImageMetadata`, an\n * `id`. The resolver gets both already awaited. Return null for a 404.\n */\nexport function createOgImage<Params extends Record<string, string | string[]> = Record<string, string>>(\n resolve: (params: Params, ctx: { id?: string }) => Promise<OgRouteCard | null> | OgRouteCard | null,\n options: OgImageOptions = {},\n) {\n return async function Image(props: {\n params: Params | Promise<Params>\n id?: string | Promise<string>\n }): Promise<Response> {\n const params = await props.params\n const id = props.id === undefined ? undefined : await props.id\n return renderCard(await resolve(params, { id }), options)\n }\n}\n\n/**\n * Route handler factory, `export const GET` from a `route.tsx`. Returns 404\n * when the resolver returns null, so a bad slug never renders a broken card.\n *\n * Next doesn't wire a route handler into metadata. For a card Next should\n * attach to the page by itself, use `createOgImage` in `opengraph-image.tsx`.\n */\nexport function createOgImageRoute<Ctx = { params: Promise<Record<string, string>> }>(\n resolve: Resolver<Ctx>,\n options: OgImageOptions = {},\n) {\n return async function GET(req: Request, ctx: Ctx): Promise<Response> {\n return renderCard(await resolve(req, ctx), options)\n }\n}\n"]}
|
package/dist/og/route.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/og/route-fit.ts","../../src/og/route.tsx"],"names":[],"mappings":"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","file":"route.mjs","sourcesContent":["/**\n * @sonordev/site-kit/og — title fitting for the runtime (Satori) card.\n *\n * The build-time card is fitted in the page: Chrome measures the rendered\n * title and steps it down until it fits (template.ts). The runtime card is\n * Satori, which has no DOM to measure, so its title sat at a fixed 84px,\n * uppercase, with no fitting. Beside the 430px photo that holds about 11\n * characters a line, so a real post title ran off the bottom of the card.\n * watsonhac.com, reinhart and vsf-consulting-nextjs each worked around it by\n * dropping the photo past a hand-picked length (40 or 50 characters).\n *\n * This does what the build-time fitter does, from an estimate instead of a\n * measurement: the same size range (TITLE_MAX_PX down to TITLE_MIN_PX, in 2px\n * steps), word-wrapped at an estimated glyph width against the real column\n * width and the height the kicker, subtitle and bar leave. When the title\n * can't fit beside the photo even at the floor, the photo goes and the title\n * gets the full width.\n *\n * The estimate is deliberately wide (uppercase, bold-ish) so an unusual\n * face errs toward a smaller title, never an overflowing one, and the title\n * box clips at its height as the backstop.\n *\n * Pure: no DOM, no Node built-ins. Geometry constants are the card's own\n * (route.tsx imports them), so the two can't drift.\n */\n\nimport { OG_HEIGHT, OG_WIDTH, TITLE_MAX_PX, TITLE_MIN_PX } from './config'\n\n/** The runtime card's layout, in px. route.tsx renders exactly this. */\nexport const ROUTE_CARD = {\n padX: 68,\n gap: 48,\n photoWidth: 430,\n photoHeight: 470,\n barHeight: 74,\n /** Breathing room above and below the copy column. */\n padY: 44,\n kickerPx: 24,\n kickerGap: 18,\n subtitlePx: 30,\n subtitleGap: 20,\n subtitleLineHeight: 1.3,\n titleLineHeight: 1.02,\n} as const\n\n/**\n * Average advance of an UPPERCASE glyph, in em. Calibrated against next/og's\n * bundled face and a bold sans; errs wide. Narrow glyphs are counted\n * separately so \"ILLINOIS\" isn't treated like \"MMMMMMMM\".\n */\nconst UPPER_EM = 0.68\nconst NARROW_EM = 0.32\nconst SPACE_EM = 0.3\n/** Mixed-case body text (the subtitle), in em. */\nconst BODY_EM = 0.52\n/** The kicker is uppercase with 6px letter spacing at 24px. */\nconst KICKER_EM = 0.72 + 6 / 24\n\nconst NARROW = /[IJ1.,:;'’!|()\\-]/\n\n/** Estimated width of `text` set uppercase at `px`. */\nexport function uppercaseWidth(text: string, px: number): number {\n let em = 0\n for (const ch of text.toUpperCase()) {\n em += ch === ' ' ? SPACE_EM : NARROW.test(ch) ? NARROW_EM : UPPER_EM\n }\n return em * px\n}\n\n/**\n * Lines `text` wraps to in a column `width` px wide, greedy by word, the way\n * the browser and Satori wrap. `\\n` is a hard break. Returns Infinity when a\n * single word is wider than the column: it can't wrap, so no line count fits.\n */\nexport function wrappedLines(\n text: string,\n width: number,\n measure: (s: string) => number,\n): number {\n let lines = 0\n for (const paragraph of text.split('\\n')) {\n const words = paragraph.split(/\\s+/).filter(Boolean)\n if (!words.length) {\n lines++\n continue\n }\n let current = ''\n for (const word of words) {\n if (measure(word) > width) return Infinity\n const next = current ? `${current} ${word}` : word\n if (current && measure(next) > width) {\n lines++\n current = word\n } else {\n current = next\n }\n }\n lines++\n }\n return lines\n}\n\nexport interface RouteFitInput {\n title: string\n kicker?: string\n subtitle?: string\n /** A photo was offered. */\n photo: boolean\n /** The card has a bottom bar. */\n bar: boolean\n}\n\nexport interface RouteFit {\n /** Title size in px. */\n titlePx: number\n /** Keep the photo. False when the title only fits at full width. */\n photo: boolean\n /** Height the title box may use; route.tsx clips the title to it. */\n titleMaxHeight: number\n /** The title fits at `titlePx` by the estimate (false only at the floor, full width). */\n fits: boolean\n}\n\nfunction columnWidth(photo: boolean): number {\n const inner = OG_WIDTH - ROUTE_CARD.padX * 2\n return photo ? inner - ROUTE_CARD.photoWidth - ROUTE_CARD.gap : inner\n}\n\n/** Height left for the title once the kicker and subtitle have theirs. */\nfunction titleBudget(input: RouteFitInput, width: number): number {\n const c = ROUTE_CARD\n let height = OG_HEIGHT - (input.bar ? c.barHeight : 0) - c.padY * 2\n if (input.kicker) {\n const lines = wrappedLines(input.kicker, width, s => s.length * KICKER_EM * c.kickerPx)\n height -= Math.min(lines, 2) * c.kickerPx * 1.2 + c.kickerGap\n }\n if (input.subtitle) {\n const lines = wrappedLines(input.subtitle, width, s => s.length * BODY_EM * c.subtitlePx)\n height -= Math.min(lines, 3) * c.subtitlePx * c.subtitleLineHeight + c.subtitleGap\n }\n return height\n}\n\n/** Largest title size that fits `width`, or null when none down to the floor does. */\nfunction largestFit(input: RouteFitInput, photo: boolean): { px: number; budget: number } | null {\n const width = columnWidth(photo)\n const budget = titleBudget(input, width)\n for (let px = TITLE_MAX_PX; px >= TITLE_MIN_PX; px -= 2) {\n const lines = wrappedLines(input.title, width, s => uppercaseWidth(s, px))\n if (lines * px * ROUTE_CARD.titleLineHeight <= budget) return { px, budget }\n }\n return null\n}\n\n/**\n * Size the title, and decide whether the photo stays.\n *\n * Beside the photo first, since the photo is the page's own image and worth\n * keeping. If even the floor size doesn't fit there, drop the photo and fit\n * the full width. If that fails too, the title sits at the floor and clips.\n */\nexport function fitRouteTitle(input: RouteFitInput): RouteFit {\n if (input.photo) {\n const withPhoto = largestFit(input, true)\n if (withPhoto) {\n return { titlePx: withPhoto.px, photo: true, titleMaxHeight: withPhoto.budget, fits: true }\n }\n }\n const full = largestFit(input, false)\n if (full) return { titlePx: full.px, photo: false, titleMaxHeight: full.budget, fits: true }\n return {\n titlePx: TITLE_MIN_PX,\n photo: false,\n titleMaxHeight: titleBudget(input, columnWidth(false)),\n fits: false,\n }\n}\n","/**\n * @sonordev/site-kit/og/route — tier 2: per-entity runtime OG cards.\n *\n * `sonor-setup og` covers the build-time cards: the site card and one per\n * route. This covers the cards that must vary per URL and can't wait for a\n * rebuild — one per blog post, per event, per product — rendered on demand\n * with next/og (Satori).\n *\n * TWO SHAPES, ONE CARD:\n *\n * `createOgImage` is the metadata file convention, and the one to reach for.\n * Next calls an `opengraph-image.tsx` default export with `{ params }` and\n * wires og:image and twitter:image for the page by itself, and `sonor-setup\n * og` sees the file and leaves the folder alone:\n *\n * ```tsx\n * // app/blog/[slug]/opengraph-image.tsx\n * import { createOgImage } from '@sonordev/site-kit/og/route'\n * import { getBlogPost } from '@sonordev/site-kit/blog/server'\n *\n * export { size, contentType } from '@sonordev/site-kit/og/route'\n * export const alt = 'From the Acme blog'\n *\n * export default createOgImage<{ slug: string }>(async ({ slug }) => {\n * const post = await getBlogPost(slug)\n * if (!post) return null // 404: a bad slug never renders a card\n * return {\n * theme: { bg: '#111110', text: '#faf6f0', accent: '#ad1d25' },\n * kicker: 'From the blog',\n * title: post.title,\n * photoUrl: post.featured_image, // absolute URL (Satori requirement)\n * bar: 'yourdomain.com',\n * }\n * })\n * ```\n *\n * The page then passes `images: false` to generateBlogPostMetadata: declared\n * images beat the file convention.\n *\n * `createOgImageRoute` is a plain route handler, `GET(request, { params })`,\n * for a card served at a URL of your choosing (`app/og/[slug]/route.tsx`).\n * Next doesn't wire it into metadata; the page declares it.\n *\n * Sites needed an adapter between the two before `createOgImage` existed\n * (watsonhac.com, reinhart, vsf-consulting-nextjs: a default export that\n * built a dummy Request to call the GET handler).\n *\n * SATORI CONSTRAINTS (why this is not the static template): flexbox-only CSS\n * subset, no external stylesheets, images must be absolute URLs or data URIs,\n * and fonts must be provided as ArrayBuffers via `fonts` — Satori cannot load\n * Google Fonts at runtime. Without fonts it falls back to Satori's default\n * face; brand-critical cards should pass font data (fetch the TTF once at\n * module init and reuse the promise).\n *\n * TITLE FITTING: Satori can't measure, so the title is sized from an estimate\n * (og/route-fit.ts) over the same range the build-time card uses, and the\n * photo is dropped when the title can't fit beside it. Long post titles no\n * longer run off the card.\n */\n\nimport React from 'react'\nimport { OG_HEIGHT, OG_WIDTH } from './config'\nimport { barTextColor } from './contrast'\nimport { fitRouteTitle, ROUTE_CARD } from './route-fit'\n\nexport interface OgRouteTheme {\n bg: string\n text: string\n accent: string\n muted?: string\n /** og.config.ts's surface color. Only used as a bar-text candidate. */\n surface?: string\n /**\n * Bottom-bar text, set on `accent`. Defaults to the first of `surface`,\n * `bg`, `text` that reaches WCAG AA for large text (3:1) against the\n * accent. It used to be `text`, which is unreadable wherever text and\n * accent are both strong (navy on crimson).\n */\n barText?: string\n}\n\nexport interface OgRouteCard {\n /** og.config.ts's `theme` can be passed as is. */\n theme: OgRouteTheme\n kicker?: string\n title: string\n subtitle?: string\n /** Absolute URL (Satori requirement) of the page's own image. */\n photoUrl?: string\n /** Bottom-bar text, e.g. the domain. */\n bar?: string\n}\n\nexport interface OgRouteFont {\n name: string\n data: ArrayBuffer\n weight?: number\n style?: 'normal' | 'italic'\n}\n\nexport interface OgImageOptions {\n fonts?: OgRouteFont[]\n}\n\n/** For `export { size, contentType } from '@sonordev/site-kit/og/route'` in an opengraph-image file. */\nexport const size = { width: OG_WIDTH, height: OG_HEIGHT }\nexport const contentType = 'image/png'\n\ntype Resolver<Ctx> = (req: Request, ctx: Ctx) => Promise<OgRouteCard | null> | OgRouteCard | null\n\n/**\n * Satori-safe element tree — exported for tests and for sites that want to\n * extend it inside their own ImageResponse.\n */\nexport function ogRouteElement(card: OgRouteCard): React.ReactElement {\n const { theme } = card\n const muted = theme.muted || theme.text\n // Only an absolute URL (or data URI) renders in Satori; anything else would\n // throw inside the render, so it's treated as no photo.\n const hasPhoto = !!card.photoUrl && /^(https?:|data:)/.test(card.photoUrl)\n const fit = fitRouteTitle({\n title: card.title,\n kicker: card.kicker,\n subtitle: card.subtitle,\n photo: hasPhoto,\n bar: !!card.bar,\n })\n const titleLine = fit.titlePx * ROUTE_CARD.titleLineHeight\n // Clip on a whole line, so an estimate that ran short loses a line cleanly\n // instead of spilling onto the bar.\n const titleMaxHeight = Math.max(1, Math.floor(fit.titleMaxHeight / titleLine)) * titleLine\n\n return (\n <div\n style={{\n width: '100%',\n height: '100%',\n display: 'flex',\n flexDirection: 'column',\n backgroundColor: theme.bg,\n color: theme.text,\n fontSize: 32,\n }}\n >\n <div\n style={{\n display: 'flex',\n flex: 1,\n alignItems: 'center',\n padding: `0 ${ROUTE_CARD.padX}px`,\n gap: ROUTE_CARD.gap,\n minHeight: 0,\n }}\n >\n <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minWidth: 0 }}>\n {card.kicker ? (\n <div\n style={{\n fontSize: ROUTE_CARD.kickerPx,\n letterSpacing: 6,\n textTransform: 'uppercase',\n color: theme.accent,\n marginBottom: ROUTE_CARD.kickerGap,\n fontWeight: 700,\n }}\n >\n {card.kicker}\n </div>\n ) : null}\n <div\n style={{\n display: 'flex',\n fontSize: fit.titlePx,\n fontWeight: 800,\n lineHeight: ROUTE_CARD.titleLineHeight,\n textTransform: 'uppercase',\n maxHeight: titleMaxHeight,\n overflow: 'hidden',\n }}\n >\n {card.title}\n </div>\n {card.subtitle ? (\n <div\n style={{\n fontSize: ROUTE_CARD.subtitlePx,\n marginTop: ROUTE_CARD.subtitleGap,\n color: muted,\n lineHeight: ROUTE_CARD.subtitleLineHeight,\n }}\n >\n {card.subtitle}\n </div>\n ) : null}\n </div>\n {hasPhoto && fit.photo ? (\n <img\n src={card.photoUrl}\n width={ROUTE_CARD.photoWidth}\n height={ROUTE_CARD.photoHeight}\n style={{ borderRadius: 24, objectFit: 'cover' }}\n />\n ) : null}\n </div>\n {card.bar ? (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n height: ROUTE_CARD.barHeight,\n backgroundColor: theme.accent,\n color: barTextColor(theme),\n fontSize: 30,\n fontWeight: 800,\n letterSpacing: 3,\n textTransform: 'uppercase',\n }}\n >\n {card.bar}\n </div>\n ) : null}\n </div>\n )\n}\n\n/**\n * Render a card, or 404 for null. Single source of truth for both factories:\n * the file convention and the route handler differ only in how they're called.\n */\nasync function renderCard(card: OgRouteCard | null, options: OgImageOptions): Promise<Response> {\n if (!card) return new Response('Not found', { status: 404 })\n\n // next/og is imported lazily so this module stays importable in test/node\n // contexts that lack the Next server runtime.\n const { ImageResponse } = await import('next/og')\n return new ImageResponse(ogRouteElement(card), {\n ...size,\n ...(options.fonts?.length ? { fonts: options.fonts as any } : {}),\n })\n}\n\n/**\n * Metadata file-convention factory, for `export default` from an\n * `opengraph-image.tsx` (or `twitter-image.tsx`). Next calls it with\n * `{ params }` (a promise on Next 15+) and, with `generateImageMetadata`, an\n * `id`. The resolver gets both already awaited. Return null for a 404.\n */\nexport function createOgImage<Params extends Record<string, string | string[]> = Record<string, string>>(\n resolve: (params: Params, ctx: { id?: string }) => Promise<OgRouteCard | null> | OgRouteCard | null,\n options: OgImageOptions = {},\n) {\n return async function Image(props: {\n params: Params | Promise<Params>\n id?: string | Promise<string>\n }): Promise<Response> {\n const params = await props.params\n const id = props.id === undefined ? undefined : await props.id\n return renderCard(await resolve(params, { id }), options)\n }\n}\n\n/**\n * Route handler factory, `export const GET` from a `route.tsx`. Returns 404\n * when the resolver returns null, so a bad slug never renders a broken card.\n *\n * Next doesn't wire a route handler into metadata. For a card Next should\n * attach to the page by itself, use `createOgImage` in `opengraph-image.tsx`.\n */\nexport function createOgImageRoute<Ctx = { params: Promise<Record<string, string>> }>(\n resolve: Resolver<Ctx>,\n options: OgImageOptions = {},\n) {\n return async function GET(req: Request, ctx: Ctx): Promise<Response> {\n return renderCard(await resolve(req, ctx), options)\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/og/route-fit.ts","../../src/og/route.tsx"],"names":[],"mappings":"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","file":"route.mjs","sourcesContent":["/**\n * @sonordev/site-kit/og — title fitting for the runtime (Satori) card.\n *\n * The build-time card is fitted in the page: Chrome measures the rendered\n * title and steps it down until it fits (template.ts). The runtime card is\n * Satori, which has no DOM to measure, so its title sat at a fixed 84px,\n * uppercase, with no fitting. Beside the 430px photo that holds about 11\n * characters a line, so a real post title ran off the bottom of the card.\n * watsonhac.com, reinhart and vsf-consulting-nextjs each worked around it by\n * dropping the photo past a hand-picked length (40 or 50 characters).\n *\n * This does what the build-time fitter does, from an estimate instead of a\n * measurement: the same size range (TITLE_MAX_PX down to TITLE_MIN_PX, in 2px\n * steps), word-wrapped at an estimated glyph width against the real column\n * width and the height the kicker, subtitle and bar leave. When the title\n * can't fit beside the photo even at the floor, the photo goes and the title\n * gets the full width.\n *\n * The estimate is deliberately wide (uppercase, bold-ish) so an unusual\n * face errs toward a smaller title, never an overflowing one, and the title\n * box clips at its height as the backstop.\n *\n * Pure: no DOM, no Node built-ins. Geometry constants are the card's own\n * (route.tsx imports them), so the two can't drift.\n */\n\nimport { OG_HEIGHT, OG_WIDTH, TITLE_MAX_PX, TITLE_MIN_PX } from './config'\n\n/** The runtime card's layout, in px. route.tsx renders exactly this. */\nexport const ROUTE_CARD = {\n padX: 68,\n gap: 48,\n photoWidth: 430,\n photoHeight: 470,\n barHeight: 74,\n /** Breathing room above and below the copy column. */\n padY: 44,\n kickerPx: 24,\n kickerGap: 18,\n subtitlePx: 30,\n subtitleGap: 20,\n subtitleLineHeight: 1.3,\n titleLineHeight: 1.02,\n} as const\n\n/**\n * Average advance of an UPPERCASE glyph, in em. Calibrated against next/og's\n * bundled face and a bold sans; errs wide. Narrow glyphs are counted\n * separately so \"ILLINOIS\" isn't treated like \"MMMMMMMM\".\n */\nconst UPPER_EM = 0.68\nconst NARROW_EM = 0.32\nconst SPACE_EM = 0.3\n/** Mixed-case body text (the subtitle), in em. */\nconst BODY_EM = 0.52\n/** The kicker is uppercase with 6px letter spacing at 24px. */\nconst KICKER_EM = 0.72 + 6 / 24\n\nconst NARROW = /[IJ1.,:;'’!|()\\-]/\n\n/** Estimated width of `text` set uppercase at `px`. */\nexport function uppercaseWidth(text: string, px: number): number {\n let em = 0\n for (const ch of text.toUpperCase()) {\n em += ch === ' ' ? SPACE_EM : NARROW.test(ch) ? NARROW_EM : UPPER_EM\n }\n return em * px\n}\n\n/**\n * Lines `text` wraps to in a column `width` px wide, greedy by word, the way\n * the browser and Satori wrap. `\\n` is a hard break. Returns Infinity when a\n * single word is wider than the column: it can't wrap, so no line count fits.\n */\nexport function wrappedLines(\n text: string,\n width: number,\n measure: (s: string) => number,\n): number {\n let lines = 0\n for (const paragraph of text.split('\\n')) {\n const words = paragraph.split(/\\s+/).filter(Boolean)\n if (!words.length) {\n lines++\n continue\n }\n let current = ''\n for (const word of words) {\n if (measure(word) > width) return Infinity\n const next = current ? `${current} ${word}` : word\n if (current && measure(next) > width) {\n lines++\n current = word\n } else {\n current = next\n }\n }\n lines++\n }\n return lines\n}\n\nexport interface RouteFitInput {\n title: string\n kicker?: string\n subtitle?: string\n /** A photo was offered. */\n photo: boolean\n /** The card has a bottom bar. */\n bar: boolean\n}\n\nexport interface RouteFit {\n /** Title size in px. */\n titlePx: number\n /** Keep the photo. False when the title only fits at full width. */\n photo: boolean\n /** Height the title box may use; route.tsx clips the title to it. */\n titleMaxHeight: number\n /** The title fits at `titlePx` by the estimate (false only at the floor, full width). */\n fits: boolean\n}\n\nfunction columnWidth(photo: boolean): number {\n const inner = OG_WIDTH - ROUTE_CARD.padX * 2\n return photo ? inner - ROUTE_CARD.photoWidth - ROUTE_CARD.gap : inner\n}\n\n/** Height left for the title once the kicker and subtitle have theirs. */\nfunction titleBudget(input: RouteFitInput, width: number): number {\n const c = ROUTE_CARD\n let height = OG_HEIGHT - (input.bar ? c.barHeight : 0) - c.padY * 2\n if (input.kicker) {\n const lines = wrappedLines(input.kicker, width, s => s.length * KICKER_EM * c.kickerPx)\n height -= Math.min(lines, 2) * c.kickerPx * 1.2 + c.kickerGap\n }\n if (input.subtitle) {\n const lines = wrappedLines(input.subtitle, width, s => s.length * BODY_EM * c.subtitlePx)\n height -= Math.min(lines, 3) * c.subtitlePx * c.subtitleLineHeight + c.subtitleGap\n }\n return height\n}\n\n/** Largest title size that fits `width`, or null when none down to the floor does. */\nfunction largestFit(input: RouteFitInput, photo: boolean): { px: number; budget: number } | null {\n const width = columnWidth(photo)\n const budget = titleBudget(input, width)\n for (let px = TITLE_MAX_PX; px >= TITLE_MIN_PX; px -= 2) {\n const lines = wrappedLines(input.title, width, s => uppercaseWidth(s, px))\n if (lines * px * ROUTE_CARD.titleLineHeight <= budget) return { px, budget }\n }\n return null\n}\n\n/**\n * Size the title, and decide whether the photo stays.\n *\n * Beside the photo first, since the photo is the page's own image and worth\n * keeping. If even the floor size doesn't fit there, drop the photo and fit\n * the full width. If that fails too, the title sits at the floor and clips.\n */\nexport function fitRouteTitle(input: RouteFitInput): RouteFit {\n if (input.photo) {\n const withPhoto = largestFit(input, true)\n if (withPhoto) {\n return { titlePx: withPhoto.px, photo: true, titleMaxHeight: withPhoto.budget, fits: true }\n }\n }\n const full = largestFit(input, false)\n if (full) return { titlePx: full.px, photo: false, titleMaxHeight: full.budget, fits: true }\n return {\n titlePx: TITLE_MIN_PX,\n photo: false,\n titleMaxHeight: titleBudget(input, columnWidth(false)),\n fits: false,\n }\n}\n","/**\n * @sonordev/site-kit/og/route — tier 2: per-entity runtime OG cards.\n *\n * `sonor-setup og` covers the build-time cards: the site card and one per\n * route. This covers the cards that must vary per URL and can't wait for a\n * rebuild — one per article, per event, per product — rendered on demand\n * with next/og (Satori).\n *\n * TWO SHAPES, ONE CARD:\n *\n * `createOgImage` is the metadata file convention, and the one to reach for.\n * Next calls an `opengraph-image.tsx` default export with `{ params }` and\n * wires og:image and twitter:image for the page by itself, and `sonor-setup\n * og` sees the file and leaves the folder alone:\n *\n * ```tsx\n * // app/blog/[slug]/opengraph-image.tsx\n * import { createOgImage } from '@sonordev/site-kit/og/route'\n * import { getArticle } from '@sonordev/site-kit/articles/server'\n *\n * export { size, contentType } from '@sonordev/site-kit/og/route'\n * export const alt = 'From the Acme article'\n *\n * export default createOgImage<{ slug: string }>(async ({ slug }) => {\n * const post = await getArticle(slug)\n * if (!post) return null // 404: a bad slug never renders a card\n * return {\n * theme: { bg: '#111110', text: '#faf6f0', accent: '#ad1d25' },\n * kicker: 'From the publication',\n * title: post.title,\n * photoUrl: post.featured_image, // absolute URL (Satori requirement)\n * bar: 'yourdomain.com',\n * }\n * })\n * ```\n *\n * The page then passes `images: false` to generateArticleMetadata: declared\n * images beat the file convention.\n *\n * `createOgImageRoute` is a plain route handler, `GET(request, { params })`,\n * for a card served at a URL of your choosing (`app/og/[slug]/route.tsx`).\n * Next doesn't wire it into metadata; the page declares it.\n *\n * Sites needed an adapter between the two before `createOgImage` existed\n * (watsonhac.com, reinhart, vsf-consulting-nextjs: a default export that\n * built a dummy Request to call the GET handler).\n *\n * SATORI CONSTRAINTS (why this is not the static template): flexbox-only CSS\n * subset, no external stylesheets, images must be absolute URLs or data URIs,\n * and fonts must be provided as ArrayBuffers via `fonts` — Satori cannot load\n * Google Fonts at runtime. Without fonts it falls back to Satori's default\n * face; brand-critical cards should pass font data (fetch the TTF once at\n * module init and reuse the promise).\n *\n * TITLE FITTING: Satori can't measure, so the title is sized from an estimate\n * (og/route-fit.ts) over the same range the build-time card uses, and the\n * photo is dropped when the title can't fit beside it. Long post titles no\n * longer run off the card.\n */\n\nimport React from 'react'\nimport { OG_HEIGHT, OG_WIDTH } from './config'\nimport { barTextColor } from './contrast'\nimport { fitRouteTitle, ROUTE_CARD } from './route-fit'\n\nexport interface OgRouteTheme {\n bg: string\n text: string\n accent: string\n muted?: string\n /** og.config.ts's surface color. Only used as a bar-text candidate. */\n surface?: string\n /**\n * Bottom-bar text, set on `accent`. Defaults to the first of `surface`,\n * `bg`, `text` that reaches WCAG AA for large text (3:1) against the\n * accent. It used to be `text`, which is unreadable wherever text and\n * accent are both strong (navy on crimson).\n */\n barText?: string\n}\n\nexport interface OgRouteCard {\n /** og.config.ts's `theme` can be passed as is. */\n theme: OgRouteTheme\n kicker?: string\n title: string\n subtitle?: string\n /** Absolute URL (Satori requirement) of the page's own image. */\n photoUrl?: string\n /** Bottom-bar text, e.g. the domain. */\n bar?: string\n}\n\nexport interface OgRouteFont {\n name: string\n data: ArrayBuffer\n weight?: number\n style?: 'normal' | 'italic'\n}\n\nexport interface OgImageOptions {\n fonts?: OgRouteFont[]\n}\n\n/** For `export { size, contentType } from '@sonordev/site-kit/og/route'` in an opengraph-image file. */\nexport const size = { width: OG_WIDTH, height: OG_HEIGHT }\nexport const contentType = 'image/png'\n\ntype Resolver<Ctx> = (req: Request, ctx: Ctx) => Promise<OgRouteCard | null> | OgRouteCard | null\n\n/**\n * Satori-safe element tree — exported for tests and for sites that want to\n * extend it inside their own ImageResponse.\n */\nexport function ogRouteElement(card: OgRouteCard): React.ReactElement {\n const { theme } = card\n const muted = theme.muted || theme.text\n // Only an absolute URL (or data URI) renders in Satori; anything else would\n // throw inside the render, so it's treated as no photo.\n const hasPhoto = !!card.photoUrl && /^(https?:|data:)/.test(card.photoUrl)\n const fit = fitRouteTitle({\n title: card.title,\n kicker: card.kicker,\n subtitle: card.subtitle,\n photo: hasPhoto,\n bar: !!card.bar,\n })\n const titleLine = fit.titlePx * ROUTE_CARD.titleLineHeight\n // Clip on a whole line, so an estimate that ran short loses a line cleanly\n // instead of spilling onto the bar.\n const titleMaxHeight = Math.max(1, Math.floor(fit.titleMaxHeight / titleLine)) * titleLine\n\n return (\n <div\n style={{\n width: '100%',\n height: '100%',\n display: 'flex',\n flexDirection: 'column',\n backgroundColor: theme.bg,\n color: theme.text,\n fontSize: 32,\n }}\n >\n <div\n style={{\n display: 'flex',\n flex: 1,\n alignItems: 'center',\n padding: `0 ${ROUTE_CARD.padX}px`,\n gap: ROUTE_CARD.gap,\n minHeight: 0,\n }}\n >\n <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minWidth: 0 }}>\n {card.kicker ? (\n <div\n style={{\n fontSize: ROUTE_CARD.kickerPx,\n letterSpacing: 6,\n textTransform: 'uppercase',\n color: theme.accent,\n marginBottom: ROUTE_CARD.kickerGap,\n fontWeight: 700,\n }}\n >\n {card.kicker}\n </div>\n ) : null}\n <div\n style={{\n display: 'flex',\n fontSize: fit.titlePx,\n fontWeight: 800,\n lineHeight: ROUTE_CARD.titleLineHeight,\n textTransform: 'uppercase',\n maxHeight: titleMaxHeight,\n overflow: 'hidden',\n }}\n >\n {card.title}\n </div>\n {card.subtitle ? (\n <div\n style={{\n fontSize: ROUTE_CARD.subtitlePx,\n marginTop: ROUTE_CARD.subtitleGap,\n color: muted,\n lineHeight: ROUTE_CARD.subtitleLineHeight,\n }}\n >\n {card.subtitle}\n </div>\n ) : null}\n </div>\n {hasPhoto && fit.photo ? (\n <img\n src={card.photoUrl}\n width={ROUTE_CARD.photoWidth}\n height={ROUTE_CARD.photoHeight}\n style={{ borderRadius: 24, objectFit: 'cover' }}\n />\n ) : null}\n </div>\n {card.bar ? (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n height: ROUTE_CARD.barHeight,\n backgroundColor: theme.accent,\n color: barTextColor(theme),\n fontSize: 30,\n fontWeight: 800,\n letterSpacing: 3,\n textTransform: 'uppercase',\n }}\n >\n {card.bar}\n </div>\n ) : null}\n </div>\n )\n}\n\n/**\n * Render a card, or 404 for null. Single source of truth for both factories:\n * the file convention and the route handler differ only in how they're called.\n */\nasync function renderCard(card: OgRouteCard | null, options: OgImageOptions): Promise<Response> {\n if (!card) return new Response('Not found', { status: 404 })\n\n // next/og is imported lazily so this module stays importable in test/node\n // contexts that lack the Next server runtime.\n const { ImageResponse } = await import('next/og')\n return new ImageResponse(ogRouteElement(card), {\n ...size,\n ...(options.fonts?.length ? { fonts: options.fonts as any } : {}),\n })\n}\n\n/**\n * Metadata file-convention factory, for `export default` from an\n * `opengraph-image.tsx` (or `twitter-image.tsx`). Next calls it with\n * `{ params }` (a promise on Next 15+) and, with `generateImageMetadata`, an\n * `id`. The resolver gets both already awaited. Return null for a 404.\n */\nexport function createOgImage<Params extends Record<string, string | string[]> = Record<string, string>>(\n resolve: (params: Params, ctx: { id?: string }) => Promise<OgRouteCard | null> | OgRouteCard | null,\n options: OgImageOptions = {},\n) {\n return async function Image(props: {\n params: Params | Promise<Params>\n id?: string | Promise<string>\n }): Promise<Response> {\n const params = await props.params\n const id = props.id === undefined ? undefined : await props.id\n return renderCard(await resolve(params, { id }), options)\n }\n}\n\n/**\n * Route handler factory, `export const GET` from a `route.tsx`. Returns 404\n * when the resolver returns null, so a bad slug never renders a broken card.\n *\n * Next doesn't wire a route handler into metadata. For a card Next should\n * attach to the page by itself, use `createOgImage` in `opengraph-image.tsx`.\n */\nexport function createOgImageRoute<Ctx = { params: Promise<Record<string, string>> }>(\n resolve: Resolver<Ctx>,\n options: OgImageOptions = {},\n) {\n return async function GET(req: Request, ctx: Ctx): Promise<Response> {\n return renderCard(await resolve(req, ctx), options)\n }\n}\n"]}
|
package/dist/og/wiring.d.ts
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* 3. A site with NO per-page cards still needs the layout to reference the
|
|
23
23
|
* site card, which is the original check and still correct in that mode.
|
|
24
24
|
* 4. Next spells a code-based card's URL with no extension
|
|
25
|
-
* (`/
|
|
25
|
+
* (`/article/x/opengraph-image`), and `trailingSlash: true` 308s it. Such a
|
|
26
26
|
* page declares the slashed URL (`codeCardImage`) instead.
|
|
27
27
|
*
|
|
28
28
|
* `summary_large_image` and the code-card rules read the site's code through
|
|
@@ -71,12 +71,12 @@ export interface OgWiringFacts {
|
|
|
71
71
|
export interface OgCodeCardFacts {
|
|
72
72
|
/** Project-relative card file. */
|
|
73
73
|
file: string;
|
|
74
|
-
/** Route pattern, `/
|
|
74
|
+
/** Route pattern, `/article/*`. */
|
|
75
75
|
route: string;
|
|
76
76
|
/** The page (or a helper it imports) declares the card with `codeCardImage`. */
|
|
77
77
|
declaresUrl: boolean;
|
|
78
|
-
/** The page calls
|
|
79
|
-
|
|
78
|
+
/** The page calls generateArticleMetadata without `images: false`. */
|
|
79
|
+
articleDeclaresImages: boolean;
|
|
80
80
|
}
|
|
81
81
|
export interface OgWiringProblem {
|
|
82
82
|
message: string;
|
package/dist/og/wiring.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wiring.d.ts","sourceRoot":"","sources":["../../src/og/wiring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAOH,MAAM,WAAW,aAAa;IAC5B,6CAA6C;IAC7C,QAAQ,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,yDAAyD;IACzD,oBAAoB,EAAE,OAAO,CAAA;IAC7B;;;;OAIG;IACH,SAAS,EAAE,OAAO,CAAA;IAClB,uDAAuD;IACvD,eAAe,EAAE,OAAO,CAAA;IACxB,6BAA6B;IAC7B,WAAW,EAAE,OAAO,CAAA;IACpB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,oFAAoF;IACpF,SAAS,CAAC,EAAE,eAAe,EAAE,CAAA;IAC7B;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,
|
|
1
|
+
{"version":3,"file":"wiring.d.ts","sourceRoot":"","sources":["../../src/og/wiring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAOH,MAAM,WAAW,aAAa;IAC5B,6CAA6C;IAC7C,QAAQ,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,yDAAyD;IACzD,oBAAoB,EAAE,OAAO,CAAA;IAC7B;;;;OAIG;IACH,SAAS,EAAE,OAAO,CAAA;IAClB,uDAAuD;IACvD,eAAe,EAAE,OAAO,CAAA;IACxB,6BAA6B;IAC7B,WAAW,EAAE,OAAO,CAAA;IACpB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,oFAAoF;IACpF,SAAS,CAAC,EAAE,eAAe,EAAE,CAAA;IAC7B;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,gFAAgF;IAChF,WAAW,EAAE,OAAO,CAAA;IACpB,sEAAsE;IACtE,qBAAqB,EAAE,OAAO,CAAA;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;CACZ;AAYD,uEAAuE;AACvE,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBnD;AAwED,8EAA8E;AAC9E,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,GAAE;IAAE,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GAAG,aAAa,CAqCnG;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,eAAe,EAAE,CAoErE;AAED,6CAA6C;AAC7C,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,GAAE;IAAE,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GAAG,eAAe,EAAE,CAExG"}
|
package/dist/proxy/index.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
var chunkKP5BFPGT_js = require('../chunk-KP5BFPGT.js');
|
|
4
4
|
var chunkWS7MH3GD_js = require('../chunk-WS7MH3GD.js');
|
|
5
|
-
var
|
|
5
|
+
var chunk4BEGKFMN_js = require('../chunk-4BEGKFMN.js');
|
|
6
6
|
require('../chunk-JP5WGL75.js');
|
|
7
|
-
require('../chunk-
|
|
8
|
-
require('../chunk-
|
|
7
|
+
require('../chunk-BBBJHYPH.js');
|
|
8
|
+
require('../chunk-UZKXOZHR.js');
|
|
9
9
|
require('../chunk-SWIKKSKL.js');
|
|
10
10
|
var chunkVJMPIRSD_js = require('../chunk-VJMPIRSD.js');
|
|
11
11
|
require('../chunk-SNAQBZPT.js');
|
|
@@ -99,7 +99,7 @@ function createProxy(config) {
|
|
|
99
99
|
}
|
|
100
100
|
if (config?.redirects !== false) {
|
|
101
101
|
const redirectConfig = typeof config?.redirects === "object" ? config.redirects : {};
|
|
102
|
-
const redirect = await
|
|
102
|
+
const redirect = await chunk4BEGKFMN_js.handleManagedRedirects(
|
|
103
103
|
request,
|
|
104
104
|
redirectConfig
|
|
105
105
|
);
|
|
@@ -110,7 +110,7 @@ function createProxy(config) {
|
|
|
110
110
|
}
|
|
111
111
|
const requestHeaders = new Headers(request.headers);
|
|
112
112
|
requestHeaders.set(
|
|
113
|
-
|
|
113
|
+
chunk4BEGKFMN_js.SK_PATH_HEADER,
|
|
114
114
|
request.nextUrl.pathname + request.nextUrl.search
|
|
115
115
|
);
|
|
116
116
|
let response = server.NextResponse.next({ request: { headers: requestHeaders } });
|
package/dist/proxy/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { buildSecurityHeaders } from '../chunk-R5F2JQVX.mjs';
|
|
2
2
|
export { DEFAULT_FRAME_ANCESTORS } from '../chunk-R5F2JQVX.mjs';
|
|
3
3
|
import { buildAiDiscoveryHeaders, wantsLlmsDiscoveryLink } from '../chunk-DA7IANFG.mjs';
|
|
4
|
-
import { handleManagedRedirects, SK_PATH_HEADER } from '../chunk-
|
|
4
|
+
import { handleManagedRedirects, SK_PATH_HEADER } from '../chunk-UO6KTHBQ.mjs';
|
|
5
5
|
import '../chunk-B22WSN4L.mjs';
|
|
6
|
-
import '../chunk-
|
|
7
|
-
import '../chunk-
|
|
6
|
+
import '../chunk-AS2HUIDW.mjs';
|
|
7
|
+
import '../chunk-KZVSB5WE.mjs';
|
|
8
8
|
import '../chunk-ZN4HBK5C.mjs';
|
|
9
9
|
import { uuid } from '../chunk-6EDVCUAG.mjs';
|
|
10
10
|
import '../chunk-BEXTDHO4.mjs';
|
package/dist/redirects/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunk4BEGKFMN_js = require('../chunk-4BEGKFMN.js');
|
|
4
4
|
require('../chunk-JP5WGL75.js');
|
|
5
|
-
require('../chunk-
|
|
6
|
-
require('../chunk-
|
|
5
|
+
require('../chunk-BBBJHYPH.js');
|
|
6
|
+
require('../chunk-UZKXOZHR.js');
|
|
7
7
|
require('../chunk-SWIKKSKL.js');
|
|
8
8
|
require('../chunk-SNAQBZPT.js');
|
|
9
9
|
|
|
@@ -11,31 +11,31 @@ require('../chunk-SNAQBZPT.js');
|
|
|
11
11
|
|
|
12
12
|
Object.defineProperty(exports, "SK_PATH_HEADER", {
|
|
13
13
|
enumerable: true,
|
|
14
|
-
get: function () { return
|
|
14
|
+
get: function () { return chunk4BEGKFMN_js.SK_PATH_HEADER; }
|
|
15
15
|
});
|
|
16
16
|
Object.defineProperty(exports, "clearRedirectCache", {
|
|
17
17
|
enumerable: true,
|
|
18
|
-
get: function () { return
|
|
18
|
+
get: function () { return chunk4BEGKFMN_js.clearRedirectCache; }
|
|
19
19
|
});
|
|
20
20
|
Object.defineProperty(exports, "fetchRedirectRules", {
|
|
21
21
|
enumerable: true,
|
|
22
|
-
get: function () { return
|
|
22
|
+
get: function () { return chunk4BEGKFMN_js.fetchRedirectRules; }
|
|
23
23
|
});
|
|
24
24
|
Object.defineProperty(exports, "findRedirectMatch", {
|
|
25
25
|
enumerable: true,
|
|
26
|
-
get: function () { return
|
|
26
|
+
get: function () { return chunk4BEGKFMN_js.findRedirectMatch; }
|
|
27
27
|
});
|
|
28
28
|
Object.defineProperty(exports, "generateNextRedirects", {
|
|
29
29
|
enumerable: true,
|
|
30
|
-
get: function () { return
|
|
30
|
+
get: function () { return chunk4BEGKFMN_js.generateNextRedirects; }
|
|
31
31
|
});
|
|
32
32
|
Object.defineProperty(exports, "handleManagedRedirects", {
|
|
33
33
|
enumerable: true,
|
|
34
|
-
get: function () { return
|
|
34
|
+
get: function () { return chunk4BEGKFMN_js.handleManagedRedirects; }
|
|
35
35
|
});
|
|
36
36
|
Object.defineProperty(exports, "trackRedirectHit", {
|
|
37
37
|
enumerable: true,
|
|
38
|
-
get: function () { return
|
|
38
|
+
get: function () { return chunk4BEGKFMN_js.trackRedirectHit; }
|
|
39
39
|
});
|
|
40
40
|
//# sourceMappingURL=index.js.map
|
|
41
41
|
//# sourceMappingURL=index.js.map
|
package/dist/redirects/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { SK_PATH_HEADER, clearRedirectCache, fetchRedirectRules, findRedirectMatch, generateNextRedirects, handleManagedRedirects, trackRedirectHit } from '../chunk-
|
|
1
|
+
export { SK_PATH_HEADER, clearRedirectCache, fetchRedirectRules, findRedirectMatch, generateNextRedirects, handleManagedRedirects, trackRedirectHit } from '../chunk-UO6KTHBQ.mjs';
|
|
2
2
|
import '../chunk-B22WSN4L.mjs';
|
|
3
|
-
import '../chunk-
|
|
4
|
-
import '../chunk-
|
|
3
|
+
import '../chunk-AS2HUIDW.mjs';
|
|
4
|
+
import '../chunk-KZVSB5WE.mjs';
|
|
5
5
|
import '../chunk-ZN4HBK5C.mjs';
|
|
6
6
|
import '../chunk-BEXTDHO4.mjs';
|
|
7
7
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunk4BEGKFMN_js = require('../chunk-4BEGKFMN.js');
|
|
4
4
|
require('../chunk-JP5WGL75.js');
|
|
5
|
-
require('../chunk-
|
|
6
|
-
require('../chunk-
|
|
5
|
+
require('../chunk-BBBJHYPH.js');
|
|
6
|
+
require('../chunk-UZKXOZHR.js');
|
|
7
7
|
require('../chunk-SWIKKSKL.js');
|
|
8
8
|
require('../chunk-SNAQBZPT.js');
|
|
9
9
|
var headers = require('next/headers');
|
|
@@ -19,7 +19,7 @@ async function resolveManagedRedirect(config = {}) {
|
|
|
19
19
|
let raw;
|
|
20
20
|
try {
|
|
21
21
|
const h = await headers.headers();
|
|
22
|
-
raw = h.get(
|
|
22
|
+
raw = h.get(chunk4BEGKFMN_js.SK_PATH_HEADER);
|
|
23
23
|
} catch {
|
|
24
24
|
return null;
|
|
25
25
|
}
|
|
@@ -30,10 +30,10 @@ async function resolveManagedRedirect(config = {}) {
|
|
|
30
30
|
if (pathname.startsWith("/_next") || pathname.startsWith("/api") || pathname.includes(".")) {
|
|
31
31
|
return null;
|
|
32
32
|
}
|
|
33
|
-
const rules = await
|
|
34
|
-
const match =
|
|
33
|
+
const rules = await chunk4BEGKFMN_js.fetchRedirectRules(config);
|
|
34
|
+
const match = chunk4BEGKFMN_js.findRedirectMatch(pathname, rules);
|
|
35
35
|
if (!match) return null;
|
|
36
|
-
|
|
36
|
+
chunk4BEGKFMN_js.trackRedirectHit(config, match.from_path, config.domain).catch(() => {
|
|
37
37
|
});
|
|
38
38
|
const destination = search ? `${match.to_path}${match.to_path.includes("?") ? "&" : "?"}${search}` : match.to_path;
|
|
39
39
|
return {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { SK_PATH_HEADER, fetchRedirectRules, findRedirectMatch, trackRedirectHit } from '../chunk-
|
|
1
|
+
import { SK_PATH_HEADER, fetchRedirectRules, findRedirectMatch, trackRedirectHit } from '../chunk-UO6KTHBQ.mjs';
|
|
2
2
|
import '../chunk-B22WSN4L.mjs';
|
|
3
|
-
import '../chunk-
|
|
4
|
-
import '../chunk-
|
|
3
|
+
import '../chunk-AS2HUIDW.mjs';
|
|
4
|
+
import '../chunk-KZVSB5WE.mjs';
|
|
5
5
|
import '../chunk-ZN4HBK5C.mjs';
|
|
6
6
|
import '../chunk-BEXTDHO4.mjs';
|
|
7
7
|
import { headers } from 'next/headers';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the key is served. Sonor's IndexNowService (sonor-api
|
|
3
|
+
* src/modules/seo/indexnow.service.ts, INDEXNOW_KEY_PATH) submits this path
|
|
4
|
+
* as the keyLocation. Keep the two equal.
|
|
5
|
+
*/
|
|
6
|
+
export declare const INDEXNOW_KEY_PATH = "/indexnow.txt";
|
|
7
|
+
/** The project's IndexNow key, or null when Sonor can't be reached. */
|
|
8
|
+
export declare function fetchIndexNowKey(): Promise<string | null>;
|
|
9
|
+
/**
|
|
10
|
+
* Route handler for app/indexnow.txt/route.ts. Answers 404 while the key
|
|
11
|
+
* can't be read, so Sonor skips the host instead of submitting with a key
|
|
12
|
+
* the host doesn't actually serve.
|
|
13
|
+
*/
|
|
14
|
+
export declare function GET(): Promise<Response>;
|
|
15
|
+
/** Same handler, for sites that prefer a factory like createRobots. */
|
|
16
|
+
export declare function createIndexNowKeyRoute(): () => Promise<Response>;
|
|
17
|
+
//# sourceMappingURL=indexnow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexnow.d.ts","sourceRoot":"","sources":["../../src/robots/indexnow.ts"],"names":[],"mappings":"AAgBA;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,kBAAkB,CAAA;AAKhD,uEAAuE;AACvE,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAS/D;AAED;;;;GAIG;AACH,wBAAsB,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,CAU7C;AAED,uEAAuE;AACvE,wBAAgB,sBAAsB,IAAI,MAAM,OAAO,CAAC,QAAQ,CAAC,CAEhE"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkO5GHP7UO_js = require('../chunk-O5GHP7UO.js');
|
|
4
|
+
require('../chunk-SNAQBZPT.js');
|
|
5
|
+
|
|
6
|
+
// src/robots/indexnow.ts
|
|
7
|
+
var INDEXNOW_KEY_PATH = "/indexnow.txt";
|
|
8
|
+
var KEY_SHAPE = /^[a-zA-Z0-9-]{8,128}$/;
|
|
9
|
+
async function fetchIndexNowKey() {
|
|
10
|
+
const data = await chunkO5GHP7UO_js.serverApiFetch("/api/public/seo/indexnow-key", {
|
|
11
|
+
method: "GET",
|
|
12
|
+
revalidate: 86400,
|
|
13
|
+
retry: true,
|
|
14
|
+
logPrefix: "@sonordev/site-kit/robots/indexnow"
|
|
15
|
+
}).catch(() => null);
|
|
16
|
+
const key = data?.key?.trim();
|
|
17
|
+
return key && KEY_SHAPE.test(key) ? key : null;
|
|
18
|
+
}
|
|
19
|
+
async function GET() {
|
|
20
|
+
const key = await fetchIndexNowKey();
|
|
21
|
+
if (!key) return new Response("Not found", { status: 404, headers: { "Content-Type": "text/plain; charset=utf-8" } });
|
|
22
|
+
return new Response(key, {
|
|
23
|
+
status: 200,
|
|
24
|
+
headers: {
|
|
25
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
26
|
+
"Cache-Control": "public, max-age=3600, s-maxage=86400"
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function createIndexNowKeyRoute() {
|
|
31
|
+
return GET;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
exports.GET = GET;
|
|
35
|
+
exports.INDEXNOW_KEY_PATH = INDEXNOW_KEY_PATH;
|
|
36
|
+
exports.createIndexNowKeyRoute = createIndexNowKeyRoute;
|
|
37
|
+
exports.fetchIndexNowKey = fetchIndexNowKey;
|
|
38
|
+
//# sourceMappingURL=indexnow.js.map
|
|
39
|
+
//# sourceMappingURL=indexnow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/robots/indexnow.ts"],"names":["serverApiFetch"],"mappings":";;;;;;AAqBO,IAAM,iBAAA,GAAoB;AAGjC,IAAM,SAAA,GAAY,uBAAA;AAGlB,eAAsB,gBAAA,GAA2C;AAC/D,EAAA,MAAM,IAAA,GAAO,MAAMA,+BAAA,CAAwC,8BAAA,EAAgC;AAAA,IACzF,MAAA,EAAQ,KAAA;AAAA,IACR,UAAA,EAAY,KAAA;AAAA,IACZ,KAAA,EAAO,IAAA;AAAA,IACP,SAAA,EAAW;AAAA,GACZ,CAAA,CAAE,KAAA,CAAM,MAAM,IAAI,CAAA;AACnB,EAAA,MAAM,GAAA,GAAM,IAAA,EAAM,GAAA,EAAK,IAAA,EAAK;AAC5B,EAAA,OAAO,GAAA,IAAO,SAAA,CAAU,IAAA,CAAK,GAAG,IAAI,GAAA,GAAM,IAAA;AAC5C;AAOA,eAAsB,GAAA,GAAyB;AAC7C,EAAA,MAAM,GAAA,GAAM,MAAM,gBAAA,EAAiB;AACnC,EAAA,IAAI,CAAC,GAAA,EAAK,OAAO,IAAI,SAAS,WAAA,EAAa,EAAE,MAAA,EAAQ,GAAA,EAAK,OAAA,EAAS,EAAE,cAAA,EAAgB,2BAAA,IAA+B,CAAA;AACpH,EAAA,OAAO,IAAI,SAAS,GAAA,EAAK;AAAA,IACvB,MAAA,EAAQ,GAAA;AAAA,IACR,OAAA,EAAS;AAAA,MACP,cAAA,EAAgB,2BAAA;AAAA,MAChB,eAAA,EAAiB;AAAA;AACnB,GACD,CAAA;AACH;AAGO,SAAS,sBAAA,GAAkD;AAChE,EAAA,OAAO,GAAA;AACT","file":"indexnow.js","sourcesContent":["/**\n * IndexNow key file. Sonor tells Bing (and through it ChatGPT search and\n * Copilot), Yandex, Seznam and Naver about a post the moment it's published,\n * edited or removed. IndexNow only accepts that from a host that serves the\n * project's key, and Sonor checks this file before it submits anything, so a\n * site without it is skipped rather than refused.\n *\n * @example\n * // app/indexnow.txt/route.ts — entire file\n * export { GET } from '@sonordev/site-kit/robots/indexnow'\n *\n * The key isn't a secret: IndexNow only asks that the host serve it. It's\n * read from Sonor with the site's SONOR_API_KEY and cached for a day.\n */\nimport { serverApiFetch } from '../server/server-fetch'\n\n/**\n * Where the key is served. Sonor's IndexNowService (sonor-api\n * src/modules/seo/indexnow.service.ts, INDEXNOW_KEY_PATH) submits this path\n * as the keyLocation. Keep the two equal.\n */\nexport const INDEXNOW_KEY_PATH = '/indexnow.txt'\n\n/** IndexNow keys are 8-128 characters of a-z, A-Z, 0-9 and dashes. */\nconst KEY_SHAPE = /^[a-zA-Z0-9-]{8,128}$/\n\n/** The project's IndexNow key, or null when Sonor can't be reached. */\nexport async function fetchIndexNowKey(): Promise<string | null> {\n const data = await serverApiFetch<{ key?: string | null }>('/api/public/seo/indexnow-key', {\n method: 'GET',\n revalidate: 86400,\n retry: true,\n logPrefix: '@sonordev/site-kit/robots/indexnow',\n }).catch(() => null)\n const key = data?.key?.trim()\n return key && KEY_SHAPE.test(key) ? key : null\n}\n\n/**\n * Route handler for app/indexnow.txt/route.ts. Answers 404 while the key\n * can't be read, so Sonor skips the host instead of submitting with a key\n * the host doesn't actually serve.\n */\nexport async function GET(): Promise<Response> {\n const key = await fetchIndexNowKey()\n if (!key) return new Response('Not found', { status: 404, headers: { 'Content-Type': 'text/plain; charset=utf-8' } })\n return new Response(key, {\n status: 200,\n headers: {\n 'Content-Type': 'text/plain; charset=utf-8',\n 'Cache-Control': 'public, max-age=3600, s-maxage=86400',\n },\n })\n}\n\n/** Same handler, for sites that prefer a factory like createRobots. */\nexport function createIndexNowKeyRoute(): () => Promise<Response> {\n return GET\n}\n"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { serverApiFetch } from '../chunk-6XNEGG5B.mjs';
|
|
2
|
+
import '../chunk-BEXTDHO4.mjs';
|
|
3
|
+
|
|
4
|
+
// src/robots/indexnow.ts
|
|
5
|
+
var INDEXNOW_KEY_PATH = "/indexnow.txt";
|
|
6
|
+
var KEY_SHAPE = /^[a-zA-Z0-9-]{8,128}$/;
|
|
7
|
+
async function fetchIndexNowKey() {
|
|
8
|
+
const data = await serverApiFetch("/api/public/seo/indexnow-key", {
|
|
9
|
+
method: "GET",
|
|
10
|
+
revalidate: 86400,
|
|
11
|
+
retry: true,
|
|
12
|
+
logPrefix: "@sonordev/site-kit/robots/indexnow"
|
|
13
|
+
}).catch(() => null);
|
|
14
|
+
const key = data?.key?.trim();
|
|
15
|
+
return key && KEY_SHAPE.test(key) ? key : null;
|
|
16
|
+
}
|
|
17
|
+
async function GET() {
|
|
18
|
+
const key = await fetchIndexNowKey();
|
|
19
|
+
if (!key) return new Response("Not found", { status: 404, headers: { "Content-Type": "text/plain; charset=utf-8" } });
|
|
20
|
+
return new Response(key, {
|
|
21
|
+
status: 200,
|
|
22
|
+
headers: {
|
|
23
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
24
|
+
"Cache-Control": "public, max-age=3600, s-maxage=86400"
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function createIndexNowKeyRoute() {
|
|
29
|
+
return GET;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { GET, INDEXNOW_KEY_PATH, createIndexNowKeyRoute, fetchIndexNowKey };
|
|
33
|
+
//# sourceMappingURL=indexnow.mjs.map
|
|
34
|
+
//# sourceMappingURL=indexnow.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/robots/indexnow.ts"],"names":[],"mappings":";;;;AAqBO,IAAM,iBAAA,GAAoB;AAGjC,IAAM,SAAA,GAAY,uBAAA;AAGlB,eAAsB,gBAAA,GAA2C;AAC/D,EAAA,MAAM,IAAA,GAAO,MAAM,cAAA,CAAwC,8BAAA,EAAgC;AAAA,IACzF,MAAA,EAAQ,KAAA;AAAA,IACR,UAAA,EAAY,KAAA;AAAA,IACZ,KAAA,EAAO,IAAA;AAAA,IACP,SAAA,EAAW;AAAA,GACZ,CAAA,CAAE,KAAA,CAAM,MAAM,IAAI,CAAA;AACnB,EAAA,MAAM,GAAA,GAAM,IAAA,EAAM,GAAA,EAAK,IAAA,EAAK;AAC5B,EAAA,OAAO,GAAA,IAAO,SAAA,CAAU,IAAA,CAAK,GAAG,IAAI,GAAA,GAAM,IAAA;AAC5C;AAOA,eAAsB,GAAA,GAAyB;AAC7C,EAAA,MAAM,GAAA,GAAM,MAAM,gBAAA,EAAiB;AACnC,EAAA,IAAI,CAAC,GAAA,EAAK,OAAO,IAAI,SAAS,WAAA,EAAa,EAAE,MAAA,EAAQ,GAAA,EAAK,OAAA,EAAS,EAAE,cAAA,EAAgB,2BAAA,IAA+B,CAAA;AACpH,EAAA,OAAO,IAAI,SAAS,GAAA,EAAK;AAAA,IACvB,MAAA,EAAQ,GAAA;AAAA,IACR,OAAA,EAAS;AAAA,MACP,cAAA,EAAgB,2BAAA;AAAA,MAChB,eAAA,EAAiB;AAAA;AACnB,GACD,CAAA;AACH;AAGO,SAAS,sBAAA,GAAkD;AAChE,EAAA,OAAO,GAAA;AACT","file":"indexnow.mjs","sourcesContent":["/**\n * IndexNow key file. Sonor tells Bing (and through it ChatGPT search and\n * Copilot), Yandex, Seznam and Naver about a post the moment it's published,\n * edited or removed. IndexNow only accepts that from a host that serves the\n * project's key, and Sonor checks this file before it submits anything, so a\n * site without it is skipped rather than refused.\n *\n * @example\n * // app/indexnow.txt/route.ts — entire file\n * export { GET } from '@sonordev/site-kit/robots/indexnow'\n *\n * The key isn't a secret: IndexNow only asks that the host serve it. It's\n * read from Sonor with the site's SONOR_API_KEY and cached for a day.\n */\nimport { serverApiFetch } from '../server/server-fetch'\n\n/**\n * Where the key is served. Sonor's IndexNowService (sonor-api\n * src/modules/seo/indexnow.service.ts, INDEXNOW_KEY_PATH) submits this path\n * as the keyLocation. Keep the two equal.\n */\nexport const INDEXNOW_KEY_PATH = '/indexnow.txt'\n\n/** IndexNow keys are 8-128 characters of a-z, A-Z, 0-9 and dashes. */\nconst KEY_SHAPE = /^[a-zA-Z0-9-]{8,128}$/\n\n/** The project's IndexNow key, or null when Sonor can't be reached. */\nexport async function fetchIndexNowKey(): Promise<string | null> {\n const data = await serverApiFetch<{ key?: string | null }>('/api/public/seo/indexnow-key', {\n method: 'GET',\n revalidate: 86400,\n retry: true,\n logPrefix: '@sonordev/site-kit/robots/indexnow',\n }).catch(() => null)\n const key = data?.key?.trim()\n return key && KEY_SHAPE.test(key) ? key : null\n}\n\n/**\n * Route handler for app/indexnow.txt/route.ts. Answers 404 while the key\n * can't be read, so Sonor skips the host instead of submitting with a key\n * the host doesn't actually serve.\n */\nexport async function GET(): Promise<Response> {\n const key = await fetchIndexNowKey()\n if (!key) return new Response('Not found', { status: 404, headers: { 'Content-Type': 'text/plain; charset=utf-8' } })\n return new Response(key, {\n status: 200,\n headers: {\n 'Content-Type': 'text/plain; charset=utf-8',\n 'Cache-Control': 'public, max-age=3600, s-maxage=86400',\n },\n })\n}\n\n/** Same handler, for sites that prefer a factory like createRobots. */\nexport function createIndexNowKeyRoute(): () => Promise<Response> {\n return GET\n}\n"]}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Design notes (4.0 exploration):
|
|
9
9
|
* - One endpoint, unified across content types the project already manages:
|
|
10
|
-
* pages (seo_pages),
|
|
10
|
+
* pages (seo_pages), articles, and commerce offerings. The site's whole
|
|
11
11
|
* navigable surface, ranked together.
|
|
12
12
|
* - Tenant scoping comes from the API key (ApiKeyGuard) — requests carry no
|
|
13
13
|
* project id, same as every public endpoint.
|
package/dist/search/contract.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkCLO3K7PF_js = require('../chunk-CLO3K7PF.js');
|
|
4
4
|
require('../chunk-SNAQBZPT.js');
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
Object.defineProperty(exports, "SEARCH_CONTRACT_VECTORS", {
|
|
9
9
|
enumerable: true,
|
|
10
|
-
get: function () { return
|
|
10
|
+
get: function () { return chunkCLO3K7PF_js.SEARCH_CONTRACT_VECTORS; }
|
|
11
11
|
});
|
|
12
12
|
Object.defineProperty(exports, "SEARCH_ENDPOINT", {
|
|
13
13
|
enumerable: true,
|
|
14
|
-
get: function () { return
|
|
14
|
+
get: function () { return chunkCLO3K7PF_js.SEARCH_ENDPOINT; }
|
|
15
15
|
});
|
|
16
16
|
//# sourceMappingURL=contract.js.map
|
|
17
17
|
//# sourceMappingURL=contract.js.map
|
package/dist/search/contract.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { SEARCH_CONTRACT_VECTORS, SEARCH_ENDPOINT } from '../chunk-
|
|
1
|
+
export { SEARCH_CONTRACT_VECTORS, SEARCH_ENDPOINT } from '../chunk-CFRJ2MK5.mjs';
|
|
2
2
|
import '../chunk-BEXTDHO4.mjs';
|
|
3
3
|
//# sourceMappingURL=contract.mjs.map
|
|
4
4
|
//# sourceMappingURL=contract.mjs.map
|