@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.
- package/LICENSE +21 -0
- package/README.md +233 -2
- package/dist-types/exports/MainMenu.d.ts +2 -0
- package/dist-types/exports/MainMenu.d.ts.map +1 -0
- package/dist-types/exports/PageData.d.ts +2 -0
- package/dist-types/exports/PageData.d.ts.map +1 -0
- package/dist-types/exports/RocketCodeBlock.d.ts +2 -0
- package/dist-types/exports/RocketCodeBlock.d.ts.map +1 -0
- package/dist-types/exports/RocketIcon.d.ts +2 -0
- package/dist-types/exports/RocketIcon.d.ts.map +1 -0
- package/dist-types/exports/RocketJsDemo.d.ts +2 -0
- package/dist-types/exports/RocketJsDemo.d.ts.map +1 -0
- package/dist-types/exports/RocketRequestDemo.d.ts +2 -0
- package/dist-types/exports/RocketRequestDemo.d.ts.map +1 -0
- package/dist-types/exports/SocialPreviewPlayground.d.ts +2 -0
- package/dist-types/exports/SocialPreviewPlayground.d.ts.map +1 -0
- package/dist-types/exports/adapters/netlify.d.ts +2 -0
- package/dist-types/exports/adapters/netlify.d.ts.map +1 -0
- package/dist-types/exports/asyncMessage.d.ts +2 -0
- package/dist-types/exports/asyncMessage.d.ts.map +1 -0
- package/dist-types/exports/component-hydration.d.ts +2 -0
- package/dist-types/exports/component-hydration.d.ts.map +1 -0
- package/dist-types/exports/components/web-awesome.d.ts +3 -0
- package/dist-types/exports/components/web-awesome.d.ts.map +1 -0
- package/dist-types/exports/components.d.ts +2 -0
- package/dist-types/exports/components.d.ts.map +1 -0
- package/dist-types/exports/config.d.ts +2 -0
- package/dist-types/exports/config.d.ts.map +1 -0
- package/dist-types/exports/debounce.d.ts +2 -0
- package/dist-types/exports/debounce.d.ts.map +1 -0
- package/dist-types/exports/define/RocketCodeBlock.d.ts +2 -0
- package/dist-types/exports/define/RocketCodeBlock.d.ts.map +1 -0
- package/dist-types/exports/define/RocketIcon.d.ts +2 -0
- package/dist-types/exports/define/RocketIcon.d.ts.map +1 -0
- package/dist-types/exports/define/RocketJsDemo.d.ts +2 -0
- package/dist-types/exports/define/RocketJsDemo.d.ts.map +1 -0
- package/dist-types/exports/define/RocketRequestDemo.d.ts +2 -0
- package/dist-types/exports/define/RocketRequestDemo.d.ts.map +1 -0
- package/dist-types/exports/define/menus.d.ts +2 -0
- package/dist-types/exports/define/menus.d.ts.map +1 -0
- package/dist-types/exports/extractCode.d.ts +2 -0
- package/dist-types/exports/extractCode.d.ts.map +1 -0
- package/dist-types/exports/globalData.d.ts +2 -0
- package/dist-types/exports/globalData.d.ts.map +1 -0
- package/dist-types/exports/hydration/hydrationLoader.d.ts +2 -0
- package/dist-types/exports/hydration/hydrationLoader.d.ts.map +1 -0
- package/dist-types/exports/icons.d.ts +2 -0
- package/dist-types/exports/icons.d.ts.map +1 -0
- package/dist-types/exports/layout-helper.d.ts +2 -0
- package/dist-types/exports/layout-helper.d.ts.map +1 -0
- package/dist-types/exports/layout.d.ts +2 -0
- package/dist-types/exports/layout.d.ts.map +1 -0
- package/dist-types/exports/layouts/atlasDoc.d.ts +2 -0
- package/dist-types/exports/layouts/atlasDoc.d.ts.map +1 -0
- package/dist-types/exports/layouts/atlasHero.d.ts +2 -0
- package/dist-types/exports/layouts/atlasHero.d.ts.map +1 -0
- package/dist-types/exports/layouts/atlasNotFound.d.ts +2 -0
- package/dist-types/exports/layouts/atlasNotFound.d.ts.map +1 -0
- package/dist-types/exports/loaded-page-module.d.ts +2 -0
- package/dist-types/exports/loaded-page-module.d.ts.map +1 -0
- package/dist-types/exports/markdownHook.d.ts +2 -0
- package/dist-types/exports/markdownHook.d.ts.map +1 -0
- package/dist-types/exports/menu.d.ts +2 -0
- package/dist-types/exports/menu.d.ts.map +1 -0
- package/dist-types/exports/menus.d.ts +6 -0
- package/dist-types/exports/menus.d.ts.map +1 -0
- package/dist-types/exports/page-runtime.d.ts +2 -0
- package/dist-types/exports/page-runtime.d.ts.map +1 -0
- package/dist-types/exports/pages.d.ts +2 -0
- package/dist-types/exports/pages.d.ts.map +1 -0
- package/dist-types/exports/resolve.d.ts +2 -0
- package/dist-types/exports/resolve.d.ts.map +1 -0
- package/dist-types/exports/ssr.d.ts +2 -0
- package/dist-types/exports/ssr.d.ts.map +1 -0
- package/dist-types/exports/standalone-demo-url.d.ts +2 -0
- package/dist-types/exports/standalone-demo-url.d.ts.map +1 -0
- package/dist-types/exports/transform.d.ts +2 -0
- package/dist-types/exports/transform.d.ts.map +1 -0
- package/dist-types/exports/types/hydration.d.ts +23 -0
- package/dist-types/exports/types/hydration.d.ts.map +1 -0
- package/dist-types/exports/types/rocket.d.ts +504 -0
- package/dist-types/exports/types/rocket.d.ts.map +1 -0
- package/dist-types/exports/types.d.ts +3 -0
- package/dist-types/exports/types.d.ts.map +1 -0
- package/dist-types/exports/wds-plugin.d.ts +2 -0
- package/dist-types/exports/wds-plugin.d.ts.map +1 -0
- package/dist-types/src/PageData.d.ts +82 -0
- package/dist-types/src/PageData.d.ts.map +1 -0
- package/dist-types/src/RocketCodeBlock.d.ts +64 -0
- package/dist-types/src/RocketCodeBlock.d.ts.map +1 -0
- package/dist-types/src/RocketIcon.d.ts +35 -0
- package/dist-types/src/RocketIcon.d.ts.map +1 -0
- package/dist-types/src/RocketJsDemo.d.ts +59 -0
- package/dist-types/src/RocketJsDemo.d.ts.map +1 -0
- package/dist-types/src/RocketJsDemo.test-browser.d.ts +3 -0
- package/dist-types/src/RocketJsDemo.test-browser.d.ts.map +1 -0
- package/dist-types/src/RocketRequestDemo.d.ts +57 -0
- package/dist-types/src/RocketRequestDemo.d.ts.map +1 -0
- package/dist-types/src/RocketRequestDemo.test-browser.d.ts +3 -0
- package/dist-types/src/RocketRequestDemo.test-browser.d.ts.map +1 -0
- package/dist-types/src/SocialPreviewPlayground.d.ts +102 -0
- package/dist-types/src/SocialPreviewPlayground.d.ts.map +1 -0
- package/dist-types/src/adapters/netlify.d.ts +54 -0
- package/dist-types/src/adapters/netlify.d.ts.map +1 -0
- package/dist-types/src/asyncMessage.d.ts +14 -0
- package/dist-types/src/asyncMessage.d.ts.map +1 -0
- package/dist-types/src/cli/RocketBuild.d.ts +78 -0
- package/dist-types/src/cli/RocketBuild.d.ts.map +1 -0
- package/dist-types/src/cli/RocketCli.d.ts +17 -0
- package/dist-types/src/cli/RocketCli.d.ts.map +1 -0
- package/dist-types/src/cli/RocketInit.d.ts +22 -0
- package/dist-types/src/cli/RocketInit.d.ts.map +1 -0
- package/dist-types/src/cli/RocketStart.d.ts +45 -0
- package/dist-types/src/cli/RocketStart.d.ts.map +1 -0
- package/dist-types/src/cli/cli.d.ts +3 -0
- package/dist-types/src/cli/cli.d.ts.map +1 -0
- package/dist-types/src/component-hydration.d.ts +26 -0
- package/dist-types/src/component-hydration.d.ts.map +1 -0
- package/dist-types/src/components/FeatureList.d.ts +15 -0
- package/dist-types/src/components/FeatureList.d.ts.map +1 -0
- package/dist-types/src/components/Footer.d.ts +17 -0
- package/dist-types/src/components/Footer.d.ts.map +1 -0
- package/dist-types/src/components/Header.d.ts +6 -0
- package/dist-types/src/components/Header.d.ts.map +1 -0
- package/dist-types/src/components/RocketDrawer.d.ts +20 -0
- package/dist-types/src/components/RocketDrawer.d.ts.map +1 -0
- package/dist-types/src/components/RocketSocialLink.d.ts +30 -0
- package/dist-types/src/components/RocketSocialLink.d.ts.map +1 -0
- package/dist-types/src/components.d.ts +7 -0
- package/dist-types/src/components.d.ts.map +1 -0
- package/dist-types/src/config.d.ts +6 -0
- package/dist-types/src/config.d.ts.map +1 -0
- package/dist-types/src/debounce.d.ts +8 -0
- package/dist-types/src/debounce.d.ts.map +1 -0
- package/dist-types/src/defaultSocialPreviewTemplate.d.ts +31 -0
- package/dist-types/src/defaultSocialPreviewTemplate.d.ts.map +1 -0
- package/dist-types/src/development-page-module-loader.d.ts +15 -0
- package/dist-types/src/development-page-module-loader.d.ts.map +1 -0
- package/dist-types/src/extractCode.d.ts +5 -0
- package/dist-types/src/extractCode.d.ts.map +1 -0
- package/dist-types/src/hydration/evaluate.d.ts +20 -0
- package/dist-types/src/hydration/evaluate.d.ts.map +1 -0
- package/dist-types/src/hydration/extractStrategies.d.ts +5 -0
- package/dist-types/src/hydration/extractStrategies.d.ts.map +1 -0
- package/dist-types/src/hydration/hydrationLoader.d.ts +64 -0
- package/dist-types/src/hydration/hydrationLoader.d.ts.map +1 -0
- package/dist-types/src/icons.d.ts +182 -0
- package/dist-types/src/icons.d.ts.map +1 -0
- package/dist-types/src/layouts/atlas/atlasDocLayout.d.ts +45 -0
- package/dist-types/src/layouts/atlas/atlasDocLayout.d.ts.map +1 -0
- package/dist-types/src/layouts/atlas/atlasHeroLayout.d.ts +7 -0
- package/dist-types/src/layouts/atlas/atlasHeroLayout.d.ts.map +1 -0
- package/dist-types/src/layouts/atlas/atlasNotFoundLayout.d.ts +5 -0
- package/dist-types/src/layouts/atlas/atlasNotFoundLayout.d.ts.map +1 -0
- package/dist-types/src/layouts/layout-helper.d.ts +16 -0
- package/dist-types/src/layouts/layout-helper.d.ts.map +1 -0
- package/dist-types/src/layouts/layout.d.ts +7 -0
- package/dist-types/src/layouts/layout.d.ts.map +1 -0
- package/dist-types/src/loaded-page-module.d.ts +51 -0
- package/dist-types/src/loaded-page-module.d.ts.map +1 -0
- package/dist-types/src/main.d.ts +2 -0
- package/dist-types/src/main.d.ts.map +1 -0
- package/dist-types/src/markdownCompiler.d.ts +22 -0
- package/dist-types/src/markdownCompiler.d.ts.map +1 -0
- package/dist-types/src/markdownHook.d.ts +6 -0
- package/dist-types/src/markdownHook.d.ts.map +1 -0
- package/dist-types/src/menu.d.ts +22 -0
- package/dist-types/src/menu.d.ts.map +1 -0
- package/dist-types/src/menus/MainMenu.d.ts +23 -0
- package/dist-types/src/menus/MainMenu.d.ts.map +1 -0
- package/dist-types/src/menus/RocketMenu.d.ts +23 -0
- package/dist-types/src/menus/RocketMenu.d.ts.map +1 -0
- package/dist-types/src/menus/RocketNextPage.d.ts +18 -0
- package/dist-types/src/menus/RocketNextPage.d.ts.map +1 -0
- package/dist-types/src/menus/RocketPreviousPage.d.ts +18 -0
- package/dist-types/src/menus/RocketPreviousPage.d.ts.map +1 -0
- package/dist-types/src/menus/RocketToc.d.ts +54 -0
- package/dist-types/src/menus/RocketToc.d.ts.map +1 -0
- package/dist-types/src/menus/pageNavigation.d.ts +41 -0
- package/dist-types/src/menus/pageNavigation.d.ts.map +1 -0
- package/dist-types/src/page-pagination.d.ts +69 -0
- package/dist-types/src/page-pagination.d.ts.map +1 -0
- package/dist-types/src/page-runtime.d.ts +110 -0
- package/dist-types/src/page-runtime.d.ts.map +1 -0
- package/dist-types/src/pages.d.ts +10 -0
- package/dist-types/src/pages.d.ts.map +1 -0
- package/dist-types/src/publicAssets.d.ts +70 -0
- package/dist-types/src/publicAssets.d.ts.map +1 -0
- package/dist-types/src/requestDemoMetadata.d.ts +19 -0
- package/dist-types/src/requestDemoMetadata.d.ts.map +1 -0
- package/dist-types/src/resolve.d.ts +7 -0
- package/dist-types/src/resolve.d.ts.map +1 -0
- package/dist-types/src/siteDiscoverability.d.ts +33 -0
- package/dist-types/src/siteDiscoverability.d.ts.map +1 -0
- package/dist-types/src/siteHeadMetadata.d.ts +20 -0
- package/dist-types/src/siteHeadMetadata.d.ts.map +1 -0
- package/dist-types/src/socialPreviewImages.d.ts +186 -0
- package/dist-types/src/socialPreviewImages.d.ts.map +1 -0
- package/dist-types/src/socialPreviewTemplatePreview.d.ts +22 -0
- package/dist-types/src/socialPreviewTemplatePreview.d.ts.map +1 -0
- package/dist-types/src/ssr.d.ts +6 -0
- package/dist-types/src/ssr.d.ts.map +1 -0
- package/dist-types/src/standalone-demo-url.d.ts +60 -0
- package/dist-types/src/standalone-demo-url.d.ts.map +1 -0
- package/dist-types/src/static-page-module-loader.d.ts +15 -0
- package/dist-types/src/static-page-module-loader.d.ts.map +1 -0
- package/dist-types/src/transform.d.ts +10 -0
- package/dist-types/src/transform.d.ts.map +1 -0
- package/dist-types/src/urlLifecycle.d.ts +23 -0
- package/dist-types/src/urlLifecycle.d.ts.map +1 -0
- package/dist-types/src/wds-plugin.d.ts +15 -0
- package/dist-types/src/wds-plugin.d.ts.map +1 -0
- package/docs/assets/home-background.svg +1 -0
- package/docs/assets/prism-one-light.css +368 -0
- package/docs/assets/rocket-logo-dark-with-text-below.svg +8 -0
- package/docs/assets/rocket-logo-dark-with-text.svg +7 -0
- package/docs/assets/rocket-logo-dark.svg +7 -0
- package/docs/assets/rocket-logo-light-with-text-below.svg +14 -0
- package/docs/assets/rocket-logo-light-with-text.svg +13 -0
- package/docs/assets/rocket-logo-light.svg +12 -0
- package/docs/assets/rocket-text-no-logo.svg +3 -0
- package/exports/MainMenu.js +1 -0
- package/exports/PageData.js +1 -0
- package/exports/RocketCodeBlock.js +1 -0
- package/exports/RocketIcon.js +1 -0
- package/exports/RocketJsDemo.js +1 -0
- package/exports/RocketRequestDemo.js +1 -0
- package/exports/SocialPreviewPlayground.js +1 -0
- package/exports/adapters/netlify.js +1 -0
- package/exports/asyncMessage.js +1 -0
- package/exports/component-hydration.js +1 -0
- package/exports/components/web-awesome.js +63 -0
- package/exports/components.js +1 -0
- package/exports/config.js +1 -0
- package/exports/debounce.js +1 -0
- package/exports/define/RocketCodeBlock.js +2 -0
- package/exports/define/RocketIcon.js +3 -0
- package/exports/define/RocketJsDemo.js +5 -0
- package/exports/define/RocketRequestDemo.js +5 -0
- package/exports/define/menus.js +14 -0
- package/exports/extractCode.js +1 -0
- package/exports/globalData.js +1 -0
- package/exports/hydration/hydrationLoader.js +1 -0
- package/exports/icons.js +11 -0
- package/exports/layout-helper.js +1 -0
- package/exports/layout.js +1 -0
- package/exports/layouts/_atlas.css +3 -0
- package/exports/layouts/atlasDoc.js +5 -0
- package/exports/layouts/atlasHero.js +1 -0
- package/exports/layouts/atlasNotFound.js +4 -0
- package/exports/loaded-page-module.js +5 -0
- package/exports/markdownHook.js +4 -0
- package/exports/menu.js +4 -0
- package/exports/menus.js +5 -0
- package/exports/page-runtime.js +5 -0
- package/exports/pages.js +1 -0
- package/exports/resolve.js +1 -0
- package/exports/ssr.js +1 -0
- package/exports/standalone-demo-url.js +10 -0
- package/exports/transform.js +1 -0
- package/exports/types/hydration.ts +26 -0
- package/exports/types/rocket.ts +598 -0
- package/exports/types.ts +71 -0
- package/exports/wds-plugin.js +1 -0
- package/package.json +192 -9
- package/src/PageData.js +244 -0
- package/src/RocketCodeBlock.js +516 -0
- package/src/RocketIcon.js +291 -0
- package/src/RocketJsDemo.js +397 -0
- package/src/RocketJsDemo.test-browser.js +228 -0
- package/src/RocketRequestDemo.js +439 -0
- package/src/RocketRequestDemo.test-browser.js +301 -0
- package/src/SocialPreviewPlayground.js +573 -0
- package/src/adapters/netlify.js +814 -0
- package/src/asyncMessage.js +21 -0
- package/src/cli/RocketBuild.js +581 -0
- package/src/cli/RocketCli.js +47 -0
- package/src/cli/RocketInit.js +636 -0
- package/src/cli/RocketStart.js +145 -0
- package/src/cli/cli.js +7 -0
- package/src/component-hydration.js +86 -0
- package/src/components/FeatureList.js +114 -0
- package/src/components/Footer.js +116 -0
- package/src/components/Header.js +122 -0
- package/src/components/RocketDrawer.js +193 -0
- package/src/components/RocketSocialLink.js +128 -0
- package/src/components/assets/discord.svg +7 -0
- package/src/components/assets/github.svg +4 -0
- package/src/components/assets/gitlab.svg +1 -0
- package/src/components/assets/info.txt +1 -0
- package/src/components/assets/license.svg +3 -0
- package/src/components/assets/npm.svg +5 -0
- package/src/components/assets/slack.svg +5 -0
- package/src/components/assets/telegram.svg +4 -0
- package/src/components/assets/twitter.svg +1 -0
- package/src/components.js +34 -0
- package/src/config.js +319 -0
- package/src/debounce.js +21 -0
- package/src/defaultSocialPreviewTemplate.js +118 -0
- package/src/development-page-module-loader.js +29 -0
- package/src/extractCode.js +41 -0
- package/src/hydration/evaluate.js +54 -0
- package/src/hydration/extractStrategies.js +91 -0
- package/src/hydration/hydrationLoader.js +330 -0
- package/src/icons.js +898 -0
- package/src/layouts/atlas/atlasDoc.css +877 -0
- package/src/layouts/atlas/atlasDocLayout.js +275 -0
- package/src/layouts/atlas/atlasHero.css +774 -0
- package/src/layouts/atlas/atlasHeroLayout.js +337 -0
- package/src/layouts/atlas/atlasNotFound.css +365 -0
- package/src/layouts/atlas/atlasNotFoundLayout.js +69 -0
- package/src/layouts/layout-helper.js +92 -0
- package/src/layouts/layout.js +52 -0
- package/src/loaded-page-module.js +97 -0
- package/src/main.js +72 -0
- package/src/markdownCompiler.js +303 -0
- package/src/markdownHook.js +148 -0
- package/src/menu.js +210 -0
- package/src/menus/MainMenu.js +58 -0
- package/src/menus/RocketMenu.js +191 -0
- package/src/menus/RocketNextPage.js +25 -0
- package/src/menus/RocketPreviousPage.js +29 -0
- package/src/menus/RocketToc.js +309 -0
- package/src/menus/pageNavigation.js +285 -0
- package/src/page-pagination.js +241 -0
- package/src/page-runtime.js +481 -0
- package/src/pages.js +537 -0
- package/src/publicAssets.js +336 -0
- package/src/requestDemoMetadata.js +97 -0
- package/src/resolve.js +15 -0
- package/src/siteDiscoverability.js +184 -0
- package/src/siteHeadMetadata.js +69 -0
- package/src/socialPreviewImages.js +482 -0
- package/src/socialPreviewTemplatePreview.js +352 -0
- package/src/ssr.js +14 -0
- package/src/standalone-demo-url.js +147 -0
- package/src/static-page-module-loader.js +29 -0
- package/src/transform.js +720 -0
- package/src/urlLifecycle.js +57 -0
- 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('&', '&')
|
|
180
|
+
.replaceAll('"', '"')
|
|
181
|
+
.replaceAll("'", ''')
|
|
182
|
+
.replaceAll('<', '<')
|
|
183
|
+
.replaceAll('>', '>');
|
|
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
|
+
}
|