@rocket/js 0.0.0 → 0.1.1

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.
Files changed (340) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +233 -2
  3. package/dist-types/exports/MainMenu.d.ts +2 -0
  4. package/dist-types/exports/MainMenu.d.ts.map +1 -0
  5. package/dist-types/exports/PageData.d.ts +2 -0
  6. package/dist-types/exports/PageData.d.ts.map +1 -0
  7. package/dist-types/exports/RocketCodeBlock.d.ts +2 -0
  8. package/dist-types/exports/RocketCodeBlock.d.ts.map +1 -0
  9. package/dist-types/exports/RocketIcon.d.ts +2 -0
  10. package/dist-types/exports/RocketIcon.d.ts.map +1 -0
  11. package/dist-types/exports/RocketJsDemo.d.ts +2 -0
  12. package/dist-types/exports/RocketJsDemo.d.ts.map +1 -0
  13. package/dist-types/exports/RocketRequestDemo.d.ts +2 -0
  14. package/dist-types/exports/RocketRequestDemo.d.ts.map +1 -0
  15. package/dist-types/exports/SocialPreviewPlayground.d.ts +2 -0
  16. package/dist-types/exports/SocialPreviewPlayground.d.ts.map +1 -0
  17. package/dist-types/exports/adapters/netlify.d.ts +2 -0
  18. package/dist-types/exports/adapters/netlify.d.ts.map +1 -0
  19. package/dist-types/exports/asyncMessage.d.ts +2 -0
  20. package/dist-types/exports/asyncMessage.d.ts.map +1 -0
  21. package/dist-types/exports/component-hydration.d.ts +2 -0
  22. package/dist-types/exports/component-hydration.d.ts.map +1 -0
  23. package/dist-types/exports/components/web-awesome.d.ts +3 -0
  24. package/dist-types/exports/components/web-awesome.d.ts.map +1 -0
  25. package/dist-types/exports/components.d.ts +2 -0
  26. package/dist-types/exports/components.d.ts.map +1 -0
  27. package/dist-types/exports/config.d.ts +2 -0
  28. package/dist-types/exports/config.d.ts.map +1 -0
  29. package/dist-types/exports/debounce.d.ts +2 -0
  30. package/dist-types/exports/debounce.d.ts.map +1 -0
  31. package/dist-types/exports/define/RocketCodeBlock.d.ts +2 -0
  32. package/dist-types/exports/define/RocketCodeBlock.d.ts.map +1 -0
  33. package/dist-types/exports/define/RocketIcon.d.ts +2 -0
  34. package/dist-types/exports/define/RocketIcon.d.ts.map +1 -0
  35. package/dist-types/exports/define/RocketJsDemo.d.ts +2 -0
  36. package/dist-types/exports/define/RocketJsDemo.d.ts.map +1 -0
  37. package/dist-types/exports/define/RocketRequestDemo.d.ts +2 -0
  38. package/dist-types/exports/define/RocketRequestDemo.d.ts.map +1 -0
  39. package/dist-types/exports/define/menus.d.ts +2 -0
  40. package/dist-types/exports/define/menus.d.ts.map +1 -0
  41. package/dist-types/exports/extractCode.d.ts +2 -0
  42. package/dist-types/exports/extractCode.d.ts.map +1 -0
  43. package/dist-types/exports/globalData.d.ts +2 -0
  44. package/dist-types/exports/globalData.d.ts.map +1 -0
  45. package/dist-types/exports/hydration/hydrationLoader.d.ts +2 -0
  46. package/dist-types/exports/hydration/hydrationLoader.d.ts.map +1 -0
  47. package/dist-types/exports/icons.d.ts +2 -0
  48. package/dist-types/exports/icons.d.ts.map +1 -0
  49. package/dist-types/exports/layout-helper.d.ts +2 -0
  50. package/dist-types/exports/layout-helper.d.ts.map +1 -0
  51. package/dist-types/exports/layout.d.ts +2 -0
  52. package/dist-types/exports/layout.d.ts.map +1 -0
  53. package/dist-types/exports/layouts/atlasDoc.d.ts +2 -0
  54. package/dist-types/exports/layouts/atlasDoc.d.ts.map +1 -0
  55. package/dist-types/exports/layouts/atlasHero.d.ts +2 -0
  56. package/dist-types/exports/layouts/atlasHero.d.ts.map +1 -0
  57. package/dist-types/exports/layouts/atlasNotFound.d.ts +2 -0
  58. package/dist-types/exports/layouts/atlasNotFound.d.ts.map +1 -0
  59. package/dist-types/exports/loaded-page-module.d.ts +2 -0
  60. package/dist-types/exports/loaded-page-module.d.ts.map +1 -0
  61. package/dist-types/exports/markdownHook.d.ts +2 -0
  62. package/dist-types/exports/markdownHook.d.ts.map +1 -0
  63. package/dist-types/exports/menu.d.ts +2 -0
  64. package/dist-types/exports/menu.d.ts.map +1 -0
  65. package/dist-types/exports/menus.d.ts +6 -0
  66. package/dist-types/exports/menus.d.ts.map +1 -0
  67. package/dist-types/exports/page-runtime.d.ts +2 -0
  68. package/dist-types/exports/page-runtime.d.ts.map +1 -0
  69. package/dist-types/exports/pages.d.ts +2 -0
  70. package/dist-types/exports/pages.d.ts.map +1 -0
  71. package/dist-types/exports/resolve.d.ts +2 -0
  72. package/dist-types/exports/resolve.d.ts.map +1 -0
  73. package/dist-types/exports/ssr.d.ts +2 -0
  74. package/dist-types/exports/ssr.d.ts.map +1 -0
  75. package/dist-types/exports/standalone-demo-url.d.ts +2 -0
  76. package/dist-types/exports/standalone-demo-url.d.ts.map +1 -0
  77. package/dist-types/exports/transform.d.ts +2 -0
  78. package/dist-types/exports/transform.d.ts.map +1 -0
  79. package/dist-types/exports/types/hydration.d.ts +23 -0
  80. package/dist-types/exports/types/hydration.d.ts.map +1 -0
  81. package/dist-types/exports/types/rocket.d.ts +504 -0
  82. package/dist-types/exports/types/rocket.d.ts.map +1 -0
  83. package/dist-types/exports/types.d.ts +3 -0
  84. package/dist-types/exports/types.d.ts.map +1 -0
  85. package/dist-types/exports/wds-plugin.d.ts +2 -0
  86. package/dist-types/exports/wds-plugin.d.ts.map +1 -0
  87. package/dist-types/src/PageData.d.ts +82 -0
  88. package/dist-types/src/PageData.d.ts.map +1 -0
  89. package/dist-types/src/RocketCodeBlock.d.ts +64 -0
  90. package/dist-types/src/RocketCodeBlock.d.ts.map +1 -0
  91. package/dist-types/src/RocketIcon.d.ts +35 -0
  92. package/dist-types/src/RocketIcon.d.ts.map +1 -0
  93. package/dist-types/src/RocketJsDemo.d.ts +59 -0
  94. package/dist-types/src/RocketJsDemo.d.ts.map +1 -0
  95. package/dist-types/src/RocketJsDemo.test-browser.d.ts +3 -0
  96. package/dist-types/src/RocketJsDemo.test-browser.d.ts.map +1 -0
  97. package/dist-types/src/RocketRequestDemo.d.ts +57 -0
  98. package/dist-types/src/RocketRequestDemo.d.ts.map +1 -0
  99. package/dist-types/src/RocketRequestDemo.test-browser.d.ts +3 -0
  100. package/dist-types/src/RocketRequestDemo.test-browser.d.ts.map +1 -0
  101. package/dist-types/src/SocialPreviewPlayground.d.ts +102 -0
  102. package/dist-types/src/SocialPreviewPlayground.d.ts.map +1 -0
  103. package/dist-types/src/adapters/netlify.d.ts +54 -0
  104. package/dist-types/src/adapters/netlify.d.ts.map +1 -0
  105. package/dist-types/src/asyncMessage.d.ts +14 -0
  106. package/dist-types/src/asyncMessage.d.ts.map +1 -0
  107. package/dist-types/src/cli/RocketBuild.d.ts +78 -0
  108. package/dist-types/src/cli/RocketBuild.d.ts.map +1 -0
  109. package/dist-types/src/cli/RocketCli.d.ts +17 -0
  110. package/dist-types/src/cli/RocketCli.d.ts.map +1 -0
  111. package/dist-types/src/cli/RocketInit.d.ts +22 -0
  112. package/dist-types/src/cli/RocketInit.d.ts.map +1 -0
  113. package/dist-types/src/cli/RocketStart.d.ts +45 -0
  114. package/dist-types/src/cli/RocketStart.d.ts.map +1 -0
  115. package/dist-types/src/cli/cli.d.ts +3 -0
  116. package/dist-types/src/cli/cli.d.ts.map +1 -0
  117. package/dist-types/src/component-hydration.d.ts +26 -0
  118. package/dist-types/src/component-hydration.d.ts.map +1 -0
  119. package/dist-types/src/components/FeatureList.d.ts +15 -0
  120. package/dist-types/src/components/FeatureList.d.ts.map +1 -0
  121. package/dist-types/src/components/Footer.d.ts +17 -0
  122. package/dist-types/src/components/Footer.d.ts.map +1 -0
  123. package/dist-types/src/components/Header.d.ts +6 -0
  124. package/dist-types/src/components/Header.d.ts.map +1 -0
  125. package/dist-types/src/components/RocketDrawer.d.ts +20 -0
  126. package/dist-types/src/components/RocketDrawer.d.ts.map +1 -0
  127. package/dist-types/src/components/RocketSocialLink.d.ts +30 -0
  128. package/dist-types/src/components/RocketSocialLink.d.ts.map +1 -0
  129. package/dist-types/src/components.d.ts +7 -0
  130. package/dist-types/src/components.d.ts.map +1 -0
  131. package/dist-types/src/config.d.ts +6 -0
  132. package/dist-types/src/config.d.ts.map +1 -0
  133. package/dist-types/src/debounce.d.ts +8 -0
  134. package/dist-types/src/debounce.d.ts.map +1 -0
  135. package/dist-types/src/defaultSocialPreviewTemplate.d.ts +31 -0
  136. package/dist-types/src/defaultSocialPreviewTemplate.d.ts.map +1 -0
  137. package/dist-types/src/development-page-module-loader.d.ts +15 -0
  138. package/dist-types/src/development-page-module-loader.d.ts.map +1 -0
  139. package/dist-types/src/extractCode.d.ts +5 -0
  140. package/dist-types/src/extractCode.d.ts.map +1 -0
  141. package/dist-types/src/hydration/evaluate.d.ts +20 -0
  142. package/dist-types/src/hydration/evaluate.d.ts.map +1 -0
  143. package/dist-types/src/hydration/extractStrategies.d.ts +5 -0
  144. package/dist-types/src/hydration/extractStrategies.d.ts.map +1 -0
  145. package/dist-types/src/hydration/hydrationLoader.d.ts +64 -0
  146. package/dist-types/src/hydration/hydrationLoader.d.ts.map +1 -0
  147. package/dist-types/src/icons.d.ts +182 -0
  148. package/dist-types/src/icons.d.ts.map +1 -0
  149. package/dist-types/src/layouts/atlas/atlasDocLayout.d.ts +45 -0
  150. package/dist-types/src/layouts/atlas/atlasDocLayout.d.ts.map +1 -0
  151. package/dist-types/src/layouts/atlas/atlasHeroLayout.d.ts +7 -0
  152. package/dist-types/src/layouts/atlas/atlasHeroLayout.d.ts.map +1 -0
  153. package/dist-types/src/layouts/atlas/atlasNotFoundLayout.d.ts +5 -0
  154. package/dist-types/src/layouts/atlas/atlasNotFoundLayout.d.ts.map +1 -0
  155. package/dist-types/src/layouts/layout-helper.d.ts +16 -0
  156. package/dist-types/src/layouts/layout-helper.d.ts.map +1 -0
  157. package/dist-types/src/layouts/layout.d.ts +7 -0
  158. package/dist-types/src/layouts/layout.d.ts.map +1 -0
  159. package/dist-types/src/loaded-page-module.d.ts +51 -0
  160. package/dist-types/src/loaded-page-module.d.ts.map +1 -0
  161. package/dist-types/src/main.d.ts +2 -0
  162. package/dist-types/src/main.d.ts.map +1 -0
  163. package/dist-types/src/markdownCompiler.d.ts +22 -0
  164. package/dist-types/src/markdownCompiler.d.ts.map +1 -0
  165. package/dist-types/src/markdownHook.d.ts +6 -0
  166. package/dist-types/src/markdownHook.d.ts.map +1 -0
  167. package/dist-types/src/menu.d.ts +22 -0
  168. package/dist-types/src/menu.d.ts.map +1 -0
  169. package/dist-types/src/menus/MainMenu.d.ts +23 -0
  170. package/dist-types/src/menus/MainMenu.d.ts.map +1 -0
  171. package/dist-types/src/menus/RocketMenu.d.ts +23 -0
  172. package/dist-types/src/menus/RocketMenu.d.ts.map +1 -0
  173. package/dist-types/src/menus/RocketNextPage.d.ts +18 -0
  174. package/dist-types/src/menus/RocketNextPage.d.ts.map +1 -0
  175. package/dist-types/src/menus/RocketPreviousPage.d.ts +18 -0
  176. package/dist-types/src/menus/RocketPreviousPage.d.ts.map +1 -0
  177. package/dist-types/src/menus/RocketToc.d.ts +54 -0
  178. package/dist-types/src/menus/RocketToc.d.ts.map +1 -0
  179. package/dist-types/src/menus/pageNavigation.d.ts +41 -0
  180. package/dist-types/src/menus/pageNavigation.d.ts.map +1 -0
  181. package/dist-types/src/page-pagination.d.ts +69 -0
  182. package/dist-types/src/page-pagination.d.ts.map +1 -0
  183. package/dist-types/src/page-runtime.d.ts +110 -0
  184. package/dist-types/src/page-runtime.d.ts.map +1 -0
  185. package/dist-types/src/pages.d.ts +10 -0
  186. package/dist-types/src/pages.d.ts.map +1 -0
  187. package/dist-types/src/publicAssets.d.ts +70 -0
  188. package/dist-types/src/publicAssets.d.ts.map +1 -0
  189. package/dist-types/src/requestDemoMetadata.d.ts +19 -0
  190. package/dist-types/src/requestDemoMetadata.d.ts.map +1 -0
  191. package/dist-types/src/resolve.d.ts +7 -0
  192. package/dist-types/src/resolve.d.ts.map +1 -0
  193. package/dist-types/src/siteDiscoverability.d.ts +33 -0
  194. package/dist-types/src/siteDiscoverability.d.ts.map +1 -0
  195. package/dist-types/src/siteHeadMetadata.d.ts +20 -0
  196. package/dist-types/src/siteHeadMetadata.d.ts.map +1 -0
  197. package/dist-types/src/socialPreviewImages.d.ts +186 -0
  198. package/dist-types/src/socialPreviewImages.d.ts.map +1 -0
  199. package/dist-types/src/socialPreviewTemplatePreview.d.ts +22 -0
  200. package/dist-types/src/socialPreviewTemplatePreview.d.ts.map +1 -0
  201. package/dist-types/src/ssr.d.ts +6 -0
  202. package/dist-types/src/ssr.d.ts.map +1 -0
  203. package/dist-types/src/standalone-demo-url.d.ts +60 -0
  204. package/dist-types/src/standalone-demo-url.d.ts.map +1 -0
  205. package/dist-types/src/static-page-module-loader.d.ts +15 -0
  206. package/dist-types/src/static-page-module-loader.d.ts.map +1 -0
  207. package/dist-types/src/transform.d.ts +10 -0
  208. package/dist-types/src/transform.d.ts.map +1 -0
  209. package/dist-types/src/urlLifecycle.d.ts +23 -0
  210. package/dist-types/src/urlLifecycle.d.ts.map +1 -0
  211. package/dist-types/src/wds-plugin.d.ts +15 -0
  212. package/dist-types/src/wds-plugin.d.ts.map +1 -0
  213. package/docs/assets/home-background.svg +1 -0
  214. package/docs/assets/prism-one-light.css +368 -0
  215. package/docs/assets/rocket-logo-dark-with-text-below.svg +8 -0
  216. package/docs/assets/rocket-logo-dark-with-text.svg +7 -0
  217. package/docs/assets/rocket-logo-dark.svg +7 -0
  218. package/docs/assets/rocket-logo-light-with-text-below.svg +14 -0
  219. package/docs/assets/rocket-logo-light-with-text.svg +13 -0
  220. package/docs/assets/rocket-logo-light.svg +12 -0
  221. package/docs/assets/rocket-text-no-logo.svg +3 -0
  222. package/exports/MainMenu.js +1 -0
  223. package/exports/PageData.js +1 -0
  224. package/exports/RocketCodeBlock.js +1 -0
  225. package/exports/RocketIcon.js +1 -0
  226. package/exports/RocketJsDemo.js +1 -0
  227. package/exports/RocketRequestDemo.js +1 -0
  228. package/exports/SocialPreviewPlayground.js +1 -0
  229. package/exports/adapters/netlify.js +1 -0
  230. package/exports/asyncMessage.js +1 -0
  231. package/exports/component-hydration.js +1 -0
  232. package/exports/components/web-awesome.js +63 -0
  233. package/exports/components.js +1 -0
  234. package/exports/config.js +1 -0
  235. package/exports/debounce.js +1 -0
  236. package/exports/define/RocketCodeBlock.js +2 -0
  237. package/exports/define/RocketIcon.js +3 -0
  238. package/exports/define/RocketJsDemo.js +5 -0
  239. package/exports/define/RocketRequestDemo.js +5 -0
  240. package/exports/define/menus.js +14 -0
  241. package/exports/extractCode.js +1 -0
  242. package/exports/globalData.js +1 -0
  243. package/exports/hydration/hydrationLoader.js +1 -0
  244. package/exports/icons.js +11 -0
  245. package/exports/layout-helper.js +1 -0
  246. package/exports/layout.js +1 -0
  247. package/exports/layouts/_atlas.css +3 -0
  248. package/exports/layouts/atlasDoc.js +5 -0
  249. package/exports/layouts/atlasHero.js +1 -0
  250. package/exports/layouts/atlasNotFound.js +4 -0
  251. package/exports/loaded-page-module.js +5 -0
  252. package/exports/markdownHook.js +4 -0
  253. package/exports/menu.js +4 -0
  254. package/exports/menus.js +5 -0
  255. package/exports/page-runtime.js +5 -0
  256. package/exports/pages.js +1 -0
  257. package/exports/resolve.js +1 -0
  258. package/exports/ssr.js +1 -0
  259. package/exports/standalone-demo-url.js +10 -0
  260. package/exports/transform.js +1 -0
  261. package/exports/types/hydration.ts +26 -0
  262. package/exports/types/rocket.ts +598 -0
  263. package/exports/types.ts +71 -0
  264. package/exports/wds-plugin.js +1 -0
  265. package/package.json +192 -9
  266. package/src/PageData.js +244 -0
  267. package/src/RocketCodeBlock.js +516 -0
  268. package/src/RocketIcon.js +291 -0
  269. package/src/RocketJsDemo.js +397 -0
  270. package/src/RocketJsDemo.test-browser.js +228 -0
  271. package/src/RocketRequestDemo.js +439 -0
  272. package/src/RocketRequestDemo.test-browser.js +301 -0
  273. package/src/SocialPreviewPlayground.js +573 -0
  274. package/src/adapters/netlify.js +814 -0
  275. package/src/asyncMessage.js +21 -0
  276. package/src/cli/RocketBuild.js +581 -0
  277. package/src/cli/RocketCli.js +47 -0
  278. package/src/cli/RocketInit.js +636 -0
  279. package/src/cli/RocketStart.js +145 -0
  280. package/src/cli/cli.js +7 -0
  281. package/src/component-hydration.js +86 -0
  282. package/src/components/FeatureList.js +114 -0
  283. package/src/components/Footer.js +116 -0
  284. package/src/components/Header.js +122 -0
  285. package/src/components/RocketDrawer.js +193 -0
  286. package/src/components/RocketSocialLink.js +128 -0
  287. package/src/components/assets/discord.svg +7 -0
  288. package/src/components/assets/github.svg +4 -0
  289. package/src/components/assets/gitlab.svg +1 -0
  290. package/src/components/assets/info.txt +1 -0
  291. package/src/components/assets/license.svg +3 -0
  292. package/src/components/assets/npm.svg +5 -0
  293. package/src/components/assets/slack.svg +5 -0
  294. package/src/components/assets/telegram.svg +4 -0
  295. package/src/components/assets/twitter.svg +1 -0
  296. package/src/components.js +34 -0
  297. package/src/config.js +319 -0
  298. package/src/debounce.js +21 -0
  299. package/src/defaultSocialPreviewTemplate.js +118 -0
  300. package/src/development-page-module-loader.js +29 -0
  301. package/src/extractCode.js +41 -0
  302. package/src/hydration/evaluate.js +54 -0
  303. package/src/hydration/extractStrategies.js +91 -0
  304. package/src/hydration/hydrationLoader.js +330 -0
  305. package/src/icons.js +898 -0
  306. package/src/layouts/atlas/atlasDoc.css +877 -0
  307. package/src/layouts/atlas/atlasDocLayout.js +275 -0
  308. package/src/layouts/atlas/atlasHero.css +774 -0
  309. package/src/layouts/atlas/atlasHeroLayout.js +337 -0
  310. package/src/layouts/atlas/atlasNotFound.css +365 -0
  311. package/src/layouts/atlas/atlasNotFoundLayout.js +69 -0
  312. package/src/layouts/layout-helper.js +92 -0
  313. package/src/layouts/layout.js +52 -0
  314. package/src/loaded-page-module.js +97 -0
  315. package/src/main.js +72 -0
  316. package/src/markdownCompiler.js +303 -0
  317. package/src/markdownHook.js +148 -0
  318. package/src/menu.js +210 -0
  319. package/src/menus/MainMenu.js +58 -0
  320. package/src/menus/RocketMenu.js +191 -0
  321. package/src/menus/RocketNextPage.js +25 -0
  322. package/src/menus/RocketPreviousPage.js +29 -0
  323. package/src/menus/RocketToc.js +309 -0
  324. package/src/menus/pageNavigation.js +285 -0
  325. package/src/page-pagination.js +241 -0
  326. package/src/page-runtime.js +481 -0
  327. package/src/pages.js +537 -0
  328. package/src/publicAssets.js +336 -0
  329. package/src/requestDemoMetadata.js +97 -0
  330. package/src/resolve.js +15 -0
  331. package/src/siteDiscoverability.js +184 -0
  332. package/src/siteHeadMetadata.js +69 -0
  333. package/src/socialPreviewImages.js +482 -0
  334. package/src/socialPreviewTemplatePreview.js +352 -0
  335. package/src/ssr.js +14 -0
  336. package/src/standalone-demo-url.js +147 -0
  337. package/src/static-page-module-loader.js +29 -0
  338. package/src/transform.js +720 -0
  339. package/src/urlLifecycle.js +57 -0
  340. package/src/wds-plugin.js +307 -0
@@ -0,0 +1,336 @@
1
+ import { copyFileSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { URLPattern } from 'urlpattern-polyfill';
4
+ import { paginatedArchivePaths } from './page-pagination.js';
5
+ import { normalizeDocumentPath, standaloneDemoPaths } from './standalone-demo-url.js';
6
+ import { needsDefaultSocialPreviewImage, publicSocialPreviewPages } from './socialPreviewImages.js';
7
+
8
+ export const PUBLIC_ASSETS_DIR = 'public';
9
+
10
+ /**
11
+ * @typedef {{
12
+ * file: string;
13
+ * relativePath: string;
14
+ * publicPath: string;
15
+ * }} PublicAsset
16
+ */
17
+
18
+ /**
19
+ * @param {string} [projectRoot]
20
+ * @returns {PublicAsset[]}
21
+ */
22
+ export function discoverPublicAssets(projectRoot = process.cwd()) {
23
+ const publicDir = path.join(projectRoot, PUBLIC_ASSETS_DIR);
24
+ if (!existsSync(publicDir)) {
25
+ return [];
26
+ }
27
+ const publicDirStats = lstatSync(publicDir);
28
+ if (publicDirStats.isSymbolicLink()) {
29
+ throw new Error(
30
+ `Public Assets directory ${PUBLIC_ASSETS_DIR} is a symbolic link. ` +
31
+ `Public Assets must be regular files.`,
32
+ );
33
+ }
34
+ if (!publicDirStats.isDirectory()) {
35
+ throw new Error(`Public Assets directory ${PUBLIC_ASSETS_DIR} must be a directory.`);
36
+ }
37
+
38
+ /** @type {PublicAsset[]} */
39
+ const assets = [];
40
+ collectPublicAssets({ publicDir, directory: publicDir, relativeDirectory: '', assets });
41
+ return assets.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
42
+ }
43
+
44
+ /**
45
+ * @param {{
46
+ * projectRoot: string;
47
+ * outDir: string;
48
+ * }} options
49
+ */
50
+ export function assertPublicAssetsOutputDir({ projectRoot, outDir }) {
51
+ const publicDir = path.resolve(projectRoot, PUBLIC_ASSETS_DIR);
52
+ const resolvedOutDir = path.resolve(outDir);
53
+ if (resolvedOutDir === publicDir || resolvedOutDir.startsWith(`${publicDir}${path.sep}`)) {
54
+ throw new Error(
55
+ `Invalid build output directory ${outDir}. ` +
56
+ `The ${PUBLIC_ASSETS_DIR} directory is reserved for Public Assets.`,
57
+ );
58
+ }
59
+ }
60
+
61
+ /**
62
+ * @param {{
63
+ * publicDir: string;
64
+ * directory: string;
65
+ * relativeDirectory: string;
66
+ * assets: PublicAsset[];
67
+ * }} options
68
+ */
69
+ function collectPublicAssets({ publicDir, directory, relativeDirectory, assets }) {
70
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
71
+ const relativePath = relativeDirectory ? `${relativeDirectory}/${entry.name}` : entry.name;
72
+ const file = path.join(publicDir, ...relativePath.split('/'));
73
+
74
+ if (entry.isSymbolicLink()) {
75
+ throw new Error(
76
+ `Public Asset ${PUBLIC_ASSETS_DIR}/${relativePath} is a symbolic link. ` +
77
+ `Public Assets must be regular files.`,
78
+ );
79
+ }
80
+
81
+ if (isExcludedDotPath(relativePath)) {
82
+ continue;
83
+ }
84
+
85
+ if (entry.isDirectory()) {
86
+ collectPublicAssets({
87
+ publicDir,
88
+ directory: file,
89
+ relativeDirectory: relativePath,
90
+ assets,
91
+ });
92
+ continue;
93
+ }
94
+
95
+ if (!entry.isFile()) {
96
+ throw new Error(
97
+ `Public Asset ${PUBLIC_ASSETS_DIR}/${relativePath} is not a regular file. ` +
98
+ `Public Assets must be regular files.`,
99
+ );
100
+ }
101
+
102
+ assets.push({
103
+ file,
104
+ relativePath,
105
+ publicPath: `/${relativePath}`,
106
+ });
107
+ }
108
+ }
109
+
110
+ /**
111
+ * @param {string} relativePath
112
+ */
113
+ function isExcludedDotPath(relativePath) {
114
+ if (relativePath === '.well-known' || relativePath.startsWith('.well-known/')) {
115
+ return false;
116
+ }
117
+ return relativePath.split('/').some(segment => segment.startsWith('.'));
118
+ }
119
+
120
+ /**
121
+ * @param {{
122
+ * publicAssets: PublicAsset[];
123
+ * pages: import('@rocket/js/types.js').PageRegistry;
124
+ * generatedPages: import('@rocket/js/types.js').PageRegistry;
125
+ * config: import('@rocket/js/types.js').ResolvedRocketConfig;
126
+ * emitRedirectFallbacks?: boolean;
127
+ * }} options
128
+ */
129
+ export function validatePublicAssetCollisions({
130
+ publicAssets,
131
+ pages,
132
+ generatedPages,
133
+ config,
134
+ emitRedirectFallbacks = true,
135
+ }) {
136
+ assertNoDuplicatePublicAssetPaths(publicAssets);
137
+
138
+ const generatedClaims = generatedOutputClaims({
139
+ pages,
140
+ generatedPages,
141
+ config,
142
+ emitRedirectFallbacks,
143
+ });
144
+
145
+ for (const asset of publicAssets) {
146
+ for (const requestPath of publicAssetRequestPaths(asset)) {
147
+ const pageCollision = matchingPage(requestPath, pages);
148
+ if (pageCollision) {
149
+ throw new Error(
150
+ `Public Asset ${PUBLIC_ASSETS_DIR}/${asset.relativePath} collides with configured ` +
151
+ `Page ${pageCollision.page.file} at ${pageCollision.routePath}.`,
152
+ );
153
+ }
154
+
155
+ const generatedCollision =
156
+ generatedClaims.get(requestPath) ||
157
+ generatedClaims.get(normalizeDirectoryRequestPath(requestPath));
158
+ if (generatedCollision) {
159
+ throw new Error(
160
+ `Public Asset ${PUBLIC_ASSETS_DIR}/${asset.relativePath} collides with ` +
161
+ `${generatedCollision} at ${requestPath}.`,
162
+ );
163
+ }
164
+ }
165
+ }
166
+ }
167
+
168
+ /**
169
+ * @param {string} requestPath
170
+ */
171
+ function normalizeDirectoryRequestPath(requestPath) {
172
+ return requestPath.endsWith('/') ? requestPath : `${path.dirname(requestPath)}/`;
173
+ }
174
+
175
+ /**
176
+ * @param {PublicAsset[]} publicAssets
177
+ */
178
+ function assertNoDuplicatePublicAssetPaths(publicAssets) {
179
+ const claimedPaths = new Map();
180
+ for (const asset of publicAssets) {
181
+ for (const requestPath of publicAssetRequestPaths(asset)) {
182
+ const existing = claimedPaths.get(requestPath);
183
+ if (existing) {
184
+ throw new Error(
185
+ `Public Asset ${PUBLIC_ASSETS_DIR}/${asset.relativePath} collides with Public Asset ` +
186
+ `${PUBLIC_ASSETS_DIR}/${existing.relativePath} at ${requestPath}.`,
187
+ );
188
+ }
189
+ claimedPaths.set(requestPath, asset);
190
+ }
191
+ }
192
+ }
193
+
194
+ /**
195
+ * @param {{
196
+ * pages: import('@rocket/js/types.js').PageRegistry;
197
+ * generatedPages: import('@rocket/js/types.js').PageRegistry;
198
+ * config: import('@rocket/js/types.js').ResolvedRocketConfig;
199
+ * emitRedirectFallbacks: boolean;
200
+ * }} options
201
+ * @returns {Map<string, string>}
202
+ */
203
+ function generatedOutputClaims({ pages, generatedPages, config, emitRedirectFallbacks }) {
204
+ const claims = new Map();
205
+
206
+ for (const [pagePath, page] of generatedPages) {
207
+ for (const demoPath of standaloneDemoPaths(pagePath, page)) {
208
+ claims.set(normalizeDocumentPath(demoPath), `Standalone Demo URL ${demoPath}`);
209
+ }
210
+ for (const archivePath of paginatedArchivePaths({ pages, page, pagePath })) {
211
+ claims.set(normalizeDocumentPath(archivePath), `generated archive Page ${archivePath}`);
212
+ }
213
+ }
214
+
215
+ for (const [pagePath, page] of publicSocialPreviewPages(pages)) {
216
+ if (
217
+ needsDefaultSocialPreviewImage({
218
+ pagePath,
219
+ page,
220
+ siteHeadMetadata: config.siteHeadMetadata,
221
+ })
222
+ ) {
223
+ claims.set('/_rocket/social-preview/', `Default Social Preview Image for Page ${pagePath}`);
224
+ }
225
+ }
226
+
227
+ for (const redirect of config.urlLifecycle?.redirects || []) {
228
+ claims.set(redirect.source, `Redirect source ${redirect.source}`);
229
+ if (emitRedirectFallbacks && !isFileOutputPath(redirect.source)) {
230
+ claims.set(normalizeDocumentPath(redirect.source), `Redirect fallback ${redirect.source}`);
231
+ }
232
+ }
233
+
234
+ if (config.siteDiscoverability?.sitemap === true) {
235
+ claims.set('/sitemap.xml', 'generated Sitemap');
236
+ }
237
+ if (config.siteDiscoverability?.robots === true) {
238
+ claims.set('/robots.txt', 'generated Robots File');
239
+ }
240
+ if (config.adapter?.name === 'netlify' && config.urlLifecycle?.redirects?.length) {
241
+ claims.set('/_redirects', 'Netlify Redirect output');
242
+ }
243
+
244
+ return claims;
245
+ }
246
+
247
+ /**
248
+ * @param {string} requestPath
249
+ * @param {import('@rocket/js/types.js').PageRegistry} pages
250
+ */
251
+ function matchingPage(requestPath, pages) {
252
+ for (const [routePath, page] of pages) {
253
+ const pattern = new URLPattern({ pathname: routePath });
254
+ if (pattern.exec(requestPath, 'http://localhost')) {
255
+ return { routePath, page };
256
+ }
257
+ }
258
+ return undefined;
259
+ }
260
+
261
+ /**
262
+ * @param {PublicAsset} asset
263
+ * @returns {string[]}
264
+ */
265
+ export function publicAssetRequestPaths(asset) {
266
+ const requestPaths = new Set([asset.publicPath]);
267
+ if (isIndexHtmlPath(asset.relativePath)) {
268
+ const documentPath = indexHtmlDocumentPath(asset.relativePath);
269
+ requestPaths.add(documentPath);
270
+ if (documentPath !== '/') {
271
+ requestPaths.add(documentPath.slice(0, -1));
272
+ }
273
+ }
274
+ return Array.from(requestPaths);
275
+ }
276
+
277
+ /**
278
+ * @param {string} relativePath
279
+ */
280
+ function isIndexHtmlPath(relativePath) {
281
+ return relativePath === 'index.html' || relativePath.endsWith('/index.html');
282
+ }
283
+
284
+ /**
285
+ * @param {string} relativePath
286
+ */
287
+ function indexHtmlDocumentPath(relativePath) {
288
+ if (relativePath === 'index.html') {
289
+ return '/';
290
+ }
291
+ return normalizeDocumentPath(`/${relativePath.slice(0, -'/index.html'.length)}`);
292
+ }
293
+
294
+ /**
295
+ * @param {{
296
+ * publicAssets: PublicAsset[];
297
+ * outDir: string;
298
+ * }} options
299
+ */
300
+ export function copyPublicAssets({ publicAssets, outDir }) {
301
+ for (const asset of publicAssets) {
302
+ const destination = path.join(outDir, ...asset.relativePath.split('/'));
303
+ if (existsSync(destination)) {
304
+ throw new Error(
305
+ `Public Asset ${PUBLIC_ASSETS_DIR}/${asset.relativePath} collides with generated ` +
306
+ `output ${asset.publicPath}.`,
307
+ );
308
+ }
309
+ mkdirSync(path.dirname(destination), { recursive: true });
310
+ copyFileSync(asset.file, destination);
311
+ }
312
+ }
313
+
314
+ /**
315
+ * @param {PublicAsset[]} publicAssets
316
+ * @param {string} requestPath
317
+ * @returns {PublicAsset | undefined}
318
+ */
319
+ export function findPublicAsset(publicAssets, requestPath) {
320
+ return publicAssets.find(asset => publicAssetRequestPaths(asset).includes(requestPath));
321
+ }
322
+
323
+ /**
324
+ * @param {PublicAsset} asset
325
+ */
326
+ export function readPublicAsset(asset) {
327
+ return readFileSync(asset.file);
328
+ }
329
+
330
+ /**
331
+ * @param {string} pagePath
332
+ * @returns {boolean}
333
+ */
334
+ function isFileOutputPath(pagePath) {
335
+ return path.extname(pagePath) !== '';
336
+ }
@@ -0,0 +1,97 @@
1
+ /** Runs on: import-hook */
2
+
3
+ /**
4
+ * @typedef {{
5
+ * url: string;
6
+ * label?: string;
7
+ * height?: number;
8
+ * }} RequestDemoMetadata
9
+ */
10
+
11
+ /**
12
+ * @param {string | null | undefined} meta
13
+ * @returns {RequestDemoMetadata}
14
+ */
15
+ export function parseRequestDemoMetadata(meta) {
16
+ const attributes = parseFenceAttributes(stripRequestDemoMarker(meta || ''));
17
+ const url = attributes.get('url');
18
+
19
+ if (url === undefined) {
20
+ throw new Error('Invalid Request Demo metadata: `url` is required.');
21
+ }
22
+ validateRequestDemoUrl(url);
23
+
24
+ const height = attributes.has('height')
25
+ ? parseRequestDemoHeight(attributes.get('height'))
26
+ : undefined;
27
+ const label = attributes.get('label') || undefined;
28
+
29
+ return {
30
+ url,
31
+ ...(label ? { label } : {}),
32
+ ...(height !== undefined ? { height } : {}),
33
+ };
34
+ }
35
+
36
+ /**
37
+ * @param {string} meta
38
+ */
39
+ function stripRequestDemoMarker(meta) {
40
+ return meta
41
+ .trim()
42
+ .replace(/^request-demo\b/, '')
43
+ .trim();
44
+ }
45
+
46
+ /**
47
+ * @param {string} source
48
+ */
49
+ function parseFenceAttributes(source) {
50
+ /** @type {Map<string, string | undefined>} */
51
+ const attributes = new Map();
52
+ const matcher = /([^\s=]+)(?:=(?:"([^"]*)"|'([^']*)'|([^\s]*)))?/g;
53
+
54
+ for (const match of source.matchAll(matcher)) {
55
+ attributes.set(match[1], match[2] ?? match[3] ?? match[4]);
56
+ }
57
+
58
+ return attributes;
59
+ }
60
+
61
+ /**
62
+ * @param {string} url
63
+ */
64
+ function validateRequestDemoUrl(url) {
65
+ if (isExternalUrl(url)) {
66
+ throw new Error(
67
+ 'Invalid Request Demo metadata: `url` must be a same-site path starting with `/`.',
68
+ );
69
+ }
70
+ if (!url.startsWith('/')) {
71
+ throw new Error(
72
+ 'Invalid Request Demo metadata: `url` must be a site-root path starting with `/`.',
73
+ );
74
+ }
75
+ if (url.includes('#')) {
76
+ throw new Error('Invalid Request Demo metadata: `url` must not include a hash.');
77
+ }
78
+ }
79
+
80
+ /**
81
+ * @param {string} url
82
+ */
83
+ function isExternalUrl(url) {
84
+ return url.startsWith('//') || /^[a-zA-Z][a-zA-Z\d.+-]*:/.test(url);
85
+ }
86
+
87
+ /**
88
+ * @param {string | undefined} height
89
+ */
90
+ function parseRequestDemoHeight(height) {
91
+ if (!height || !/^[1-9]\d*$/.test(height)) {
92
+ throw new Error(
93
+ 'Invalid Request Demo metadata: `height` must be a positive integer pixel value.',
94
+ );
95
+ }
96
+ return Number(height);
97
+ }
package/src/resolve.js ADDED
@@ -0,0 +1,15 @@
1
+ import path from 'path';
2
+ import { fileURLToPath } from 'url';
3
+
4
+ /**
5
+ * @param {string} url
6
+ * @param {ImportMeta} meta
7
+ * @returns {string} Absolute path to the file, relative to the output directory
8
+ */
9
+ export function resolve(url, meta) {
10
+ // get current directory
11
+ const outputFilePath = process.cwd();
12
+ const resolvedPath = meta.resolve(url);
13
+ const rel = path.relative(outputFilePath, fileURLToPath(resolvedPath));
14
+ return (process.env.ENVIRONMENT === 'BUILD' ? '/../' : '/') + rel.split(path.sep).join('/');
15
+ }
@@ -0,0 +1,184 @@
1
+ import { mkdirSync, writeFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import {
4
+ hasPagePagination,
5
+ paginatedArchivePath,
6
+ paginatedArchivePaths,
7
+ } from './page-pagination.js';
8
+
9
+ /**
10
+ * @param {{
11
+ * pages: import('@rocket/js/types.js').PageRegistry;
12
+ * outDir: string;
13
+ * config: import('@rocket/js/types.js').ResolvedRocketConfig;
14
+ * }} options
15
+ */
16
+ export function writeSiteDiscoverabilityOutputs({ pages, outDir, config }) {
17
+ const shouldWriteSitemap = config.siteDiscoverability?.sitemap === true;
18
+ const shouldWriteRobots = config.siteDiscoverability?.robots === true;
19
+ if (!shouldWriteSitemap && !shouldWriteRobots) {
20
+ return;
21
+ }
22
+
23
+ const siteOrigin = requireSiteOrigin(
24
+ config.siteOrigin,
25
+ shouldWriteSitemap ? 'Sitemap' : 'Robots File',
26
+ );
27
+ mkdirSync(outDir, { recursive: true });
28
+ if (shouldWriteSitemap) {
29
+ writeFileSync(path.join(outDir, 'sitemap.xml'), createSitemap({ pages, siteOrigin }));
30
+ }
31
+ if (shouldWriteRobots) {
32
+ writeFileSync(path.join(outDir, 'robots.txt'), createRobotsFile({ pages, siteOrigin }));
33
+ }
34
+ }
35
+
36
+ /**
37
+ * @param {{
38
+ * pages: import('@rocket/js/types.js').PageRegistry;
39
+ * siteOrigin: string;
40
+ * }} options
41
+ */
42
+ export function createSitemap({ pages, siteOrigin }) {
43
+ const locations = sitemapPageLocations({ pages, siteOrigin });
44
+ const urls = locations
45
+ .map(location => [' <url>', ` <loc>${escapeXml(location)}</loc>`, ' </url>'].join('\n'))
46
+ .join('\n');
47
+
48
+ return [
49
+ '<?xml version="1.0" encoding="UTF-8"?>',
50
+ '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
51
+ urls,
52
+ '</urlset>',
53
+ '',
54
+ ].join('\n');
55
+ }
56
+
57
+ /**
58
+ * @param {{
59
+ * pages?: import('@rocket/js/types.js').PageRegistry;
60
+ * siteOrigin: string;
61
+ * }} options
62
+ */
63
+ export function createRobotsFile({ pages = new Map(), siteOrigin }) {
64
+ const sitemapUrl = new URL('/sitemap.xml', `${siteOrigin}/`).href;
65
+ const disallowDirectives = robotsDisallowPaths({ pages })
66
+ .map(pagePath => `Disallow: ${pagePath}`)
67
+ .sort();
68
+
69
+ if (disallowDirectives.length === 0) {
70
+ return `Sitemap: ${sitemapUrl}\n`;
71
+ }
72
+
73
+ return ['User-agent: *', ...disallowDirectives, '', `Sitemap: ${sitemapUrl}`, ''].join('\n');
74
+ }
75
+
76
+ /**
77
+ * @param {{
78
+ * pages: import('@rocket/js/types.js').PageRegistry;
79
+ * siteOrigin: string;
80
+ * }} options
81
+ */
82
+ function sitemapPageLocations({ pages, siteOrigin }) {
83
+ return Array.from(pages.entries())
84
+ .filter(([pagePath, page]) => {
85
+ return !hasPathParameter(pagePath) && page.module.config.discoverability?.sitemap !== false;
86
+ })
87
+ .flatMap(([pagePath, page]) => discoverabilityPagePaths({ pages, pagePath, page }))
88
+ .map(pagePath => absolutePageUrl(siteOrigin, pagePath))
89
+ .sort();
90
+ }
91
+
92
+ /**
93
+ * @param {{
94
+ * pages: import('@rocket/js/types.js').PageRegistry;
95
+ * }} options
96
+ */
97
+ function robotsDisallowPaths({ pages }) {
98
+ return Array.from(pages.entries())
99
+ .filter(([, page]) => page.module.config.discoverability?.robots === 'disallow')
100
+ .flatMap(([pagePath, page]) => discoverabilityPagePaths({ pages, pagePath, page }));
101
+ }
102
+
103
+ /**
104
+ * @param {{
105
+ * pages: import('@rocket/js/types.js').PageRegistry;
106
+ * pagePath: string;
107
+ * page: import('@rocket/js/types.js').Page;
108
+ * }} options
109
+ */
110
+ function discoverabilityPagePaths({ pages, pagePath, page }) {
111
+ if (!hasPagePagination(page)) {
112
+ return [pagePath];
113
+ }
114
+ return [paginatedArchivePath(pagePath, 1), ...paginatedArchivePaths({ pages, page, pagePath })];
115
+ }
116
+
117
+ /**
118
+ * @param {string | undefined} siteOrigin
119
+ * @param {'Sitemap' | 'Robots File'} outputName
120
+ */
121
+ function requireSiteOrigin(siteOrigin, outputName) {
122
+ if (typeof siteOrigin !== 'string' || siteOrigin.trim() === '') {
123
+ throw new Error(
124
+ `Site Discoverability ${outputName} requires a Site Origin. ` +
125
+ `Add siteOrigin: 'https://example.com' to rocket-config.js.`,
126
+ );
127
+ }
128
+ return normalizeSiteOrigin(siteOrigin);
129
+ }
130
+
131
+ /**
132
+ * @param {string} siteOrigin
133
+ */
134
+ function normalizeSiteOrigin(siteOrigin) {
135
+ let url;
136
+ try {
137
+ url = new URL(siteOrigin);
138
+ } catch {
139
+ throw invalidSiteOriginError(siteOrigin);
140
+ }
141
+
142
+ if (url.origin === 'null' || url.pathname !== '/' || url.search || url.hash) {
143
+ throw invalidSiteOriginError(siteOrigin);
144
+ }
145
+ return url.origin;
146
+ }
147
+
148
+ /**
149
+ * @param {string} siteOrigin
150
+ */
151
+ function invalidSiteOriginError(siteOrigin) {
152
+ return new Error(
153
+ `Invalid Site Origin ${JSON.stringify(siteOrigin)}. ` +
154
+ `Use an absolute origin such as 'https://example.com'.`,
155
+ );
156
+ }
157
+
158
+ /**
159
+ * @param {string} siteOrigin
160
+ * @param {string} pagePath
161
+ */
162
+ function absolutePageUrl(siteOrigin, pagePath) {
163
+ const pathname = pagePath.startsWith('/') ? pagePath : `/${pagePath}`;
164
+ return new URL(pathname, `${siteOrigin}/`).href;
165
+ }
166
+
167
+ /**
168
+ * @param {string} pagePath
169
+ */
170
+ function hasPathParameter(pagePath) {
171
+ return /(^|\/):[^/]+/.test(pagePath);
172
+ }
173
+
174
+ /**
175
+ * @param {string} value
176
+ */
177
+ function escapeXml(value) {
178
+ return value
179
+ .replaceAll('&', '&amp;')
180
+ .replaceAll('"', '&quot;')
181
+ .replaceAll("'", '&apos;')
182
+ .replaceAll('<', '&lt;')
183
+ .replaceAll('>', '&gt;');
184
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @param {{
3
+ * config: import('@rocket/js/types.js').SiteHeadMetadataConfig;
4
+ * pageMetadata: import('@rocket/js/types.js').PageMetadata;
5
+ * pageSiteHeadMetadata?: import('@rocket/js/types.js').PageSiteHeadMetadataConfig;
6
+ * defaultSocialPreviewImage?: string;
7
+ * pathname: string;
8
+ * siteOrigin: string;
9
+ * }} options
10
+ * @returns {import('@rocket/js/types.js').SiteHeadMetadata}
11
+ */
12
+ export function createSiteHeadMetadata({
13
+ config,
14
+ pageMetadata,
15
+ pageSiteHeadMetadata,
16
+ defaultSocialPreviewImage,
17
+ pathname,
18
+ siteOrigin,
19
+ }) {
20
+ const socialPreviewImage = pageSiteHeadMetadata?.socialPreview?.image
21
+ ? resolveSocialPreviewImage(pageSiteHeadMetadata.socialPreview.image, pathname, siteOrigin)
22
+ : defaultSocialPreviewImage;
23
+ return {
24
+ siteName: config.siteName,
25
+ defaultDescription: config.defaultDescription,
26
+ language: config.language,
27
+ ...(config.icons ? { icons: config.icons } : {}),
28
+ ...(config.themeColor ? { themeColor: config.themeColor } : {}),
29
+ ...(pageSiteHeadMetadata?.indexing ? { indexing: pageSiteHeadMetadata.indexing } : {}),
30
+ ...(socialPreviewImage ? { socialPreview: { image: socialPreviewImage } } : {}),
31
+ title: pathname === '/' ? config.siteName : `${pageMetadata.title} | ${config.siteName}`,
32
+ description: pageMetadata.description || config.defaultDescription,
33
+ canonicalUrl: new URL(pathname, `${siteOrigin}/`).href,
34
+ };
35
+ }
36
+
37
+ /**
38
+ * @param {string} image
39
+ * @param {string} pathname
40
+ * @param {string} siteOrigin
41
+ */
42
+ function resolveSocialPreviewImage(image, pathname, siteOrigin) {
43
+ if (isAbsoluteHttpUrl(image)) {
44
+ return image;
45
+ }
46
+ if (image.startsWith('/')) {
47
+ return new URL(image, `${siteOrigin}/`).href;
48
+ }
49
+ return new URL(image, new URL(pageDocumentPath(pathname), `${siteOrigin}/`)).href;
50
+ }
51
+
52
+ /**
53
+ * @param {string} value
54
+ */
55
+ function isAbsoluteHttpUrl(value) {
56
+ try {
57
+ const url = new URL(value);
58
+ return url.protocol === 'http:' || url.protocol === 'https:';
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
63
+
64
+ /**
65
+ * @param {string} pathname
66
+ */
67
+ function pageDocumentPath(pathname) {
68
+ return pathname.endsWith('/') ? pathname : `${pathname}/`;
69
+ }