@janga/norna 0.7.11 → 0.7.13
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 +120 -108
- package/bin/norna-cli.mjs +1 -1
- package/docs/README.md +107 -75
- package/docs/assets/norna-dog-shelter.jpg +0 -0
- package/docs/commands.md +7 -4
- package/docs/configuration.md +86 -185
- package/docs/design/command-organization.md +1 -1
- package/docs/design/norna-diagram-design.md +2 -1
- package/docs/design/site-examples-structure.md +4 -1
- package/docs/engine-development.md +15 -8
- package/docs/getting-started.md +126 -144
- package/docs/how-to/embedded-site.md +35 -0
- package/docs/publishing.md +47 -46
- package/docs/requirements.md +54 -0
- package/docs/routes.md +1 -1
- package/docs/site-structure.md +4 -4
- package/docs/sitewide-content.md +2 -1
- package/docs/theme.md +18 -0
- package/examples/README.md +26 -24
- package/examples/complete-sites/dog-shelter-multi-page/.astro/content-assets.mjs +4 -1
- package/examples/complete-sites/dog-shelter-multi-page/.astro/content.d.ts +1 -1
- package/examples/complete-sites/dog-shelter-multi-page/.astro/data-store.json +1 -1
- package/examples/complete-sites/dog-shelter-multi-page/.astro/dev.json +6 -8
- package/examples/complete-sites/dog-shelter-multi-page/.astro/dev.log +26 -53
- package/examples/complete-sites/dog-shelter-multi-page/README.md +7 -0
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/_metadata.json +29 -35
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/{astro-D_GIpJIE.js → astro-B3JHJyiv.js} +4 -4
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro-B3JHJyiv.js.map +1 -0
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_aria-query.js +151 -151
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -1
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_axobject-query.js +131 -131
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -1
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_html-escaper.js +1 -1
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -1
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +10 -10
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -1
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/{audit-DM9Hkl5B.js → audit-C3SVr7dT.js} +10 -10
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/audit-C3SVr7dT.js.map +1 -0
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/{highlight-DnZ1ULOL.js → highlight-BLtvqtzO.js} +1 -1
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/highlight-BLtvqtzO.js.map +1 -0
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/{icons-RyebKi45.js → icons-C92i61-2.js} +1 -1
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/icons-C92i61-2.js.map +1 -0
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/{settings-DaU-gNnK.js → settings-v5GgKks_.js} +2 -2
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/settings-v5GgKks_.js.map +1 -0
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/{toolbar-63PA619H.js → toolbar-BFvJVp26.js} +2 -2
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/toolbar-BFvJVp26.js.map +1 -0
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/{ui-library--bkcNSYz.js → ui-library-Cd35oAvo.js} +10 -10
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/ui-library-Cd35oAvo.js.map +1 -0
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/{window-jPkpKVcy.js → window-vo3A6TMn.js} +1 -1
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/window-vo3A6TMn.js.map +1 -0
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/{xray-BXCHpEJP.js → xray-DwhzGckb.js} +3 -3
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/xray-DwhzGckb.js.map +1 -0
- package/examples/complete-sites/dog-shelter-multi-page/site/.norna/generated-images.json +92 -12
- package/examples/complete-sites/dog-shelter-multi-page/site/config.md +3 -0
- package/examples/complete-sites/dog-shelter-multi-page/site/content.md +12 -0
- package/examples/complete-sites/dog-shelter-multi-page/site/images/intro/shelter-dog.jpg +0 -0
- package/examples/complete-sites/dog-shelter-multi-page/site/public/logo.svg +9 -0
- package/examples/complete-sites/dog-shelter-multi-page/site/routes/010-dogs/images/dogs/rex.jpg +0 -0
- package/examples/complete-sites/dog-shelter-multi-page/site/routes/010-dogs/images/dogs/rover.jpg +0 -0
- package/examples/complete-sites/dog-shelter-multi-page/site/routes/010-dogs/route-content.md +11 -19
- package/examples/complete-sites/dog-shelter-multi-page/site/routes/020-adopt/images/adopt/adoption-dog.jpg +0 -0
- package/examples/complete-sites/dog-shelter-multi-page/site/routes/020-adopt/route-content.md +11 -0
- package/examples/complete-sites/dog-shelter-multi-page/site/sitewide-content.md +12 -2
- package/examples/complete-sites/dog-shelter-multi-page/site/theme.md +1 -23
- package/examples/complete-sites/dog-shelter-single-page/.astro/collections/site.schema.json +59 -0
- package/examples/complete-sites/dog-shelter-single-page/.astro/collections/sitewide.schema.json +138 -0
- package/examples/complete-sites/dog-shelter-single-page/.astro/collections/theme.schema.json +755 -0
- package/examples/complete-sites/dog-shelter-single-page/.astro/content-assets.mjs +4 -0
- package/examples/complete-sites/dog-shelter-single-page/.astro/content-modules.mjs +1 -0
- package/examples/complete-sites/dog-shelter-single-page/.astro/content.d.ts +183 -0
- package/examples/complete-sites/dog-shelter-single-page/.astro/data-store.json +1 -0
- package/examples/complete-sites/dog-shelter-single-page/.astro/dev.json +13 -0
- package/examples/complete-sites/dog-shelter-single-page/.astro/dev.log +14 -0
- package/examples/complete-sites/dog-shelter-single-page/.astro/settings.json +5 -0
- package/examples/complete-sites/dog-shelter-single-page/.astro/types.d.ts +2 -0
- package/examples/complete-sites/dog-shelter-single-page/README.md +7 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/_metadata.json +74 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro-B3JHJyiv.js +434 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro-B3JHJyiv.js.map +1 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_axobject-query.js +2702 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/audit-C3SVr7dT.js +1534 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/audit-C3SVr7dT.js.map +1 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/highlight-BLtvqtzO.js +48 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/highlight-BLtvqtzO.js.map +1 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/icons-C92i61-2.js +42 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/icons-C92i61-2.js.map +1 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/package.json +3 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +4 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/settings-v5GgKks_.js +190 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/settings-v5GgKks_.js.map +1 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/toolbar-BFvJVp26.js +458 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/toolbar-BFvJVp26.js.map +1 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/ui-library-Cd35oAvo.js +1016 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/ui-library-Cd35oAvo.js.map +1 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/window-vo3A6TMn.js +32 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/window-vo3A6TMn.js.map +1 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/xray-DwhzGckb.js +111 -0
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/xray-DwhzGckb.js.map +1 -0
- package/examples/complete-sites/dog-shelter-single-page/site/.norna/generated-images.json +44 -12
- package/examples/complete-sites/dog-shelter-single-page/site/config.md +3 -0
- package/examples/complete-sites/dog-shelter-single-page/site/content.md +20 -24
- package/examples/complete-sites/dog-shelter-single-page/site/images/what-we-do/rescue-dog.jpg +0 -0
- package/examples/complete-sites/dog-shelter-single-page/site/images/you-can-help/foster-dog.jpg +0 -0
- package/examples/complete-sites/dog-shelter-single-page/site/public/logo.svg +9 -0
- package/examples/complete-sites/dog-shelter-single-page/site/sitewide-content.md +12 -2
- package/examples/complete-sites/dog-shelter-single-page/site/theme.md +1 -23
- package/examples/feature-demos/media-and-surfaces/.astro/data-store.json +1 -1
- package/examples/feature-demos/media-and-surfaces/.astro/dev.log +15 -2
- package/examples/feature-demos/media-and-surfaces/README.md +7 -1
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/_metadata.json +7 -1
- package/examples/{complete-sites/dog-shelter-multi-page → feature-demos/media-and-surfaces}/node_modules/.vite/deps/embla-carousel.js +1 -1
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/embla-carousel.js.map +1 -0
- package/examples/feature-demos/media-and-surfaces/site/config.md +3 -0
- package/examples/feature-demos/media-and-surfaces/site/content.md +5 -51
- package/examples/feature-demos/media-and-surfaces/site/routes/010-media/route-content.md +10 -25
- package/examples/feature-demos/media-and-surfaces/site/routes/020-surfaces/route-content.md +1 -1
- package/examples/feature-demos/sitewide-content/.astro/collections/site.schema.json +59 -0
- package/examples/feature-demos/sitewide-content/.astro/collections/sitewide.schema.json +138 -0
- package/examples/feature-demos/sitewide-content/.astro/collections/theme.schema.json +755 -0
- package/examples/feature-demos/sitewide-content/.astro/content-assets.mjs +1 -0
- package/examples/feature-demos/sitewide-content/.astro/content-modules.mjs +1 -0
- package/examples/feature-demos/sitewide-content/.astro/content.d.ts +183 -0
- package/examples/feature-demos/sitewide-content/.astro/data-store.json +1 -0
- package/examples/feature-demos/sitewide-content/.astro/dev.json +13 -0
- package/examples/feature-demos/sitewide-content/.astro/dev.log +18 -0
- package/examples/feature-demos/sitewide-content/.astro/settings.json +5 -0
- package/examples/feature-demos/sitewide-content/.astro/types.d.ts +2 -0
- package/examples/feature-demos/sitewide-content/README.md +18 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/_metadata.json +74 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro-B3JHJyiv.js +434 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro-B3JHJyiv.js.map +1 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_axobject-query.js +2702 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/audit-C3SVr7dT.js +1534 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/audit-C3SVr7dT.js.map +1 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/highlight-BLtvqtzO.js +48 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/highlight-BLtvqtzO.js.map +1 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/icons-C92i61-2.js +42 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/icons-C92i61-2.js.map +1 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/package.json +3 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +4 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/settings-v5GgKks_.js +190 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/settings-v5GgKks_.js.map +1 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/toolbar-BFvJVp26.js +458 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/toolbar-BFvJVp26.js.map +1 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/ui-library-Cd35oAvo.js +1016 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/ui-library-Cd35oAvo.js.map +1 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/window-vo3A6TMn.js +32 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/window-vo3A6TMn.js.map +1 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/xray-DwhzGckb.js +111 -0
- package/examples/feature-demos/sitewide-content/node_modules/.vite/deps/xray-DwhzGckb.js.map +1 -0
- package/examples/feature-demos/sitewide-content/site/.norna/generated-images.json +1 -0
- package/examples/feature-demos/sitewide-content/site/config.md +3 -0
- package/examples/feature-demos/sitewide-content/site/content.md +16 -0
- package/examples/feature-demos/sitewide-content/site/public/logo.svg +6 -0
- package/examples/feature-demos/sitewide-content/site/public/robots.txt +2 -0
- package/examples/feature-demos/sitewide-content/site/routes/010-identity/route-content.md +20 -0
- package/examples/feature-demos/sitewide-content/site/routes/020-notices/route-content.md +22 -0
- package/examples/feature-demos/sitewide-content/site/routes/030-footer/route-content.md +17 -0
- package/examples/feature-demos/sitewide-content/site/sitewide-content.md +33 -0
- package/examples/feature-demos/sitewide-content/site/theme.md +3 -0
- package/examples/feature-demos/theme-presets/.astro/dev.log +37 -0
- package/examples/feature-demos/theme-presets/README.md +5 -1
- package/examples/feature-demos/theme-presets/site/config.md +3 -0
- package/fixtures/basic/site/config.md +3 -0
- package/fixtures/content-model-v2/site/config.md +3 -0
- package/package.json +12 -7
- package/scripts/build-pages.mjs +4 -6
- package/scripts/check-config.mjs +1 -10
- package/scripts/deploy-site.mjs +12 -8
- package/scripts/lib/example-sites.mjs +3 -0
- package/scripts/lib/github-repository.mjs +44 -0
- package/scripts/lib/norna-markdown-blocks.mjs +97 -10
- package/scripts/lib/project-config.mjs +112 -123
- package/scripts/lib/site-content.mjs +7 -0
- package/scripts/lib/site-paths.mjs +6 -2
- package/scripts/test-client-javascript.mjs +221 -0
- package/scripts/test-content-check.mjs +103 -0
- package/scripts/test-content-model-v2.mjs +9 -13
- package/scripts/test-documentation.mjs +116 -0
- package/scripts/test-engine-commands.mjs +12 -3
- package/scripts/test-examples.mjs +9 -0
- package/scripts/test-package-check.mjs +17 -9
- package/scripts/test-project-config.mjs +125 -0
- package/scripts/test-temporary-visibility.mjs +3 -10
- package/scripts/test-theme-presets.mjs +3 -8
- package/scripts/watch-pages-deploy.mjs +15 -11
- package/src/components/GalleryGrid.astro +30 -230
- package/src/components/ImageCarousel.astro +184 -0
- package/src/components/MobileSectionNavigationScript.astro +14 -0
- package/src/components/SiteNavigation.astro +54 -341
- package/src/components/SiteNotesScript.astro +43 -0
- package/src/components/SitePage.astro +4 -1
- package/src/components/SiteSection.astro +4 -47
- package/src/layouts/BaseLayout.astro +1 -1
- package/src/lib/galleryImages.ts +53 -0
- package/src/styles/global.css +81 -1
- package/starters/basic/.github/workflows/deploy.yml +1 -2
- package/starters/basic/README.md +10 -8
- package/starters/basic/site/config.md +3 -0
- package/starters/project/.github/workflows/deploy.yml +1 -2
- package/starters/project/README.md +3 -4
- package/starters/project/site/config.md +3 -0
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/astro-D_GIpJIE.js.map +0 -1
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +0 -1
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/embla-carousel.js.map +0 -1
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +0 -1
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/icons-RyebKi45.js.map +0 -1
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/settings-DaU-gNnK.js.map +0 -1
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/toolbar-63PA619H.js.map +0 -1
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +0 -1
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/window-jPkpKVcy.js.map +0 -1
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/xray-BXCHpEJP.js.map +0 -1
- package/examples/complete-sites/dog-shelter-multi-page/site/config.mjs +0 -30
- package/examples/complete-sites/dog-shelter-multi-page/site/routes/010-dogs/images/dogs/rex.svg +0 -19
- package/examples/complete-sites/dog-shelter-multi-page/site/routes/010-dogs/images/dogs/rover.svg +0 -19
- package/examples/complete-sites/dog-shelter-single-page/site/config.mjs +0 -30
- package/examples/complete-sites/dog-shelter-single-page/site/images/what-we-do/dog-house.svg +0 -25
- package/examples/complete-sites/dog-shelter-single-page/site/images/you-can-help/heart.svg +0 -13
- package/examples/feature-demos/media-and-surfaces/site/config.mjs +0 -30
- package/examples/feature-demos/media-and-surfaces/site/routes/010-media/theme.md +0 -3
- package/examples/feature-demos/theme-presets/site/config.mjs +0 -30
- package/fixtures/basic/site/config.mjs +0 -27
- package/fixtures/content-model-v2/site/config.mjs +0 -11
- package/starters/basic/site/config.mjs +0 -27
- package/starters/project/site/config.mjs +0 -30
package/scripts/deploy-site.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
extractNornaMarkdownBlocks,
|
|
13
13
|
getNornaBlockImageReferences,
|
|
14
14
|
} from './lib/norna-markdown-blocks.mjs';
|
|
15
|
-
import {
|
|
15
|
+
import { getGitHubRepositoryContext, githubPagesWorkflow } from './lib/github-repository.mjs';
|
|
16
16
|
import {
|
|
17
17
|
engineRoot,
|
|
18
18
|
generatedImagesManifestLabel,
|
|
@@ -28,9 +28,9 @@ import {
|
|
|
28
28
|
const execFileAsync = promisify(execFile);
|
|
29
29
|
|
|
30
30
|
const root = siteProjectRoot;
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
const pagesWorkflow = githubPagesWorkflow;
|
|
32
|
+
let branch;
|
|
33
|
+
let repo;
|
|
34
34
|
const args = process.argv.slice(2);
|
|
35
35
|
const mode = args[0] === 'commit' ? 'commit' : 'deploy';
|
|
36
36
|
const modeArgs = mode === 'commit' ? args.slice(1) : args;
|
|
@@ -53,9 +53,9 @@ const failedConclusions = new Set(['action_required', 'cancelled', 'failure', 's
|
|
|
53
53
|
const deployUsage = [
|
|
54
54
|
'Usage: norna deploy',
|
|
55
55
|
'',
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
'Publishes an already committed default branch: builds, verifies a clean worktree,',
|
|
57
|
+
'pushes it when local HEAD is ahead of origin, and checks GitHub Pages.',
|
|
58
|
+
'If local HEAD already matches origin, it skips push and checks Pages.',
|
|
59
59
|
'',
|
|
60
60
|
'For the old build-and-commit convenience flow, use:',
|
|
61
61
|
'norna deploy:commit "Commit message"',
|
|
@@ -63,7 +63,7 @@ const deployUsage = [
|
|
|
63
63
|
const deployCommitUsage = [
|
|
64
64
|
'Usage: norna deploy:commit "Commit message"',
|
|
65
65
|
'',
|
|
66
|
-
|
|
66
|
+
'Builds, stages only allowed site changes, commits, pushes the default branch,',
|
|
67
67
|
'and checks GitHub Pages.',
|
|
68
68
|
].join('\n');
|
|
69
69
|
|
|
@@ -385,6 +385,10 @@ if (modeArgs.includes('--help') || modeArgs.includes('-h')) {
|
|
|
385
385
|
}
|
|
386
386
|
|
|
387
387
|
try {
|
|
388
|
+
const githubRepository = await getGitHubRepositoryContext({ cwd: root });
|
|
389
|
+
branch = githubRepository.branch;
|
|
390
|
+
repo = githubRepository.repo;
|
|
391
|
+
|
|
388
392
|
if (mode === 'commit') {
|
|
389
393
|
await deployWithCommit();
|
|
390
394
|
} else {
|
|
@@ -13,6 +13,9 @@ export const getExampleSites = async (root) => {
|
|
|
13
13
|
|
|
14
14
|
for (const entry of entries.filter((candidate) => candidate.isDirectory()).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
15
15
|
const siteDirectory = path.join(categoryDirectory, entry.name, 'site');
|
|
16
|
+
if (!existsSync(path.join(siteDirectory, 'config.md'))) {
|
|
17
|
+
throw new Error(`Example ${path.relative(root, siteDirectory)} is missing config.md.`);
|
|
18
|
+
}
|
|
16
19
|
if (!existsSync(path.join(siteDirectory, 'content.md'))) {
|
|
17
20
|
throw new Error(`Example ${path.relative(root, siteDirectory)} is missing content.md.`);
|
|
18
21
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
|
|
6
|
+
export const githubPagesWorkflow = 'deploy.yml';
|
|
7
|
+
|
|
8
|
+
export const getGitHubRepositoryContext = async ({ cwd, repo } = {}) => {
|
|
9
|
+
const args = [
|
|
10
|
+
'repo',
|
|
11
|
+
'view',
|
|
12
|
+
...(repo ? [repo] : []),
|
|
13
|
+
'--json',
|
|
14
|
+
'nameWithOwner,defaultBranchRef',
|
|
15
|
+
];
|
|
16
|
+
let stdout;
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
({ stdout } = await execFileAsync('gh', args, {
|
|
20
|
+
cwd,
|
|
21
|
+
maxBuffer: 1024 * 1024,
|
|
22
|
+
}));
|
|
23
|
+
} catch (error) {
|
|
24
|
+
throw new Error([
|
|
25
|
+
'Could not discover the GitHub repository and its default branch.',
|
|
26
|
+
'Run this command in a Git repository with a GitHub remote and an authenticated gh CLI, or pass explicit deploy:watch options.',
|
|
27
|
+
error.stderr?.trim(),
|
|
28
|
+
error.message,
|
|
29
|
+
].filter(Boolean).join('\n'));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const result = JSON.parse(stdout || '{}');
|
|
33
|
+
const defaultBranch = result.defaultBranchRef?.name;
|
|
34
|
+
const nameWithOwner = result.nameWithOwner;
|
|
35
|
+
|
|
36
|
+
if (!nameWithOwner || !defaultBranch) {
|
|
37
|
+
throw new Error('GitHub did not report a repository name and default branch.');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return Object.freeze({
|
|
41
|
+
branch: defaultBranch,
|
|
42
|
+
repo: nameWithOwner,
|
|
43
|
+
});
|
|
44
|
+
};
|
|
@@ -589,7 +589,8 @@ export const splitNornaMarkdownBlockMarkers = (html, options = {}) => {
|
|
|
589
589
|
const normalizeBlockSource = (source) => source.replace(/\r\n?/g, '\n').trim();
|
|
590
590
|
|
|
591
591
|
const inlineNoteReferenceRegex = /\{note-ref\}/g;
|
|
592
|
-
const
|
|
592
|
+
const inlineNoteDeclarationOpeningRegex = /^\s*\{note:\s*(.*)$/;
|
|
593
|
+
const inlineNoteDeclarationClosingRegex = /^(.*?)\}\s*$/;
|
|
593
594
|
const inlineNoteDeclarationStartRegex = /\{note(?:\s*:|\s*\})/;
|
|
594
595
|
|
|
595
596
|
const maskInlineCodeAndComments = (source) => source.replace(
|
|
@@ -605,7 +606,82 @@ const formatInlineNoteError = (message, options, line) => ({
|
|
|
605
606
|
message: failMessage(message, { ...options, line }),
|
|
606
607
|
});
|
|
607
608
|
|
|
608
|
-
const getInlineNoteErrorMessage = () => 'Write the note on its own line as "{note:
|
|
609
|
+
const getInlineNoteErrorMessage = () => 'Write the note on its own line as "{note: Short text.}", or end a multiline note with "}".';
|
|
610
|
+
|
|
611
|
+
const findUnescapedInlineNoteConstruct = (source) => {
|
|
612
|
+
const constructRegex = /\{note(?:-ref\}|\s*:|\s*\})/g;
|
|
613
|
+
|
|
614
|
+
for (const match of source.matchAll(constructRegex)) {
|
|
615
|
+
const index = match.index ?? 0;
|
|
616
|
+
let precedingBackslashes = 0;
|
|
617
|
+
for (let cursor = index - 1; cursor >= 0 && source[cursor] === '\\'; cursor -= 1) {
|
|
618
|
+
precedingBackslashes += 1;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (precedingBackslashes % 2 === 0) {
|
|
622
|
+
return match[0].startsWith('{note-ref') ? 'reference' : 'note';
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return null;
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
const collectInlineNoteDeclaration = (lines, maskedLines, startIndex) => {
|
|
630
|
+
const openingMatch = (lines[startIndex] ?? '').match(inlineNoteDeclarationOpeningRegex);
|
|
631
|
+
if (!openingMatch) return null;
|
|
632
|
+
const maskedOpeningMatch = (maskedLines[startIndex] ?? '').match(inlineNoteDeclarationOpeningRegex);
|
|
633
|
+
|
|
634
|
+
const textLines = [];
|
|
635
|
+
let currentLine = openingMatch[1] ?? '';
|
|
636
|
+
let currentMaskedLine = maskedOpeningMatch?.[1] ?? '';
|
|
637
|
+
|
|
638
|
+
for (let index = startIndex; index < lines.length; index += 1) {
|
|
639
|
+
const conflictType = findUnescapedInlineNoteConstruct(currentMaskedLine);
|
|
640
|
+
if (conflictType) {
|
|
641
|
+
return {
|
|
642
|
+
closed: false,
|
|
643
|
+
conflictIndex: index,
|
|
644
|
+
conflictType,
|
|
645
|
+
endIndex: index,
|
|
646
|
+
text: textLines.map((line) => line.trim()).filter(Boolean).join(' '),
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const closingMatch = currentLine.match(inlineNoteDeclarationClosingRegex);
|
|
651
|
+
if (closingMatch) {
|
|
652
|
+
textLines.push(closingMatch[1]);
|
|
653
|
+
return {
|
|
654
|
+
closed: true,
|
|
655
|
+
endIndex: index,
|
|
656
|
+
text: textLines.map((line) => line.trim()).filter(Boolean).join(' '),
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
textLines.push(currentLine);
|
|
661
|
+
currentLine = lines[index + 1] ?? '';
|
|
662
|
+
currentMaskedLine = maskedLines[index + 1] ?? '';
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return {
|
|
666
|
+
closed: false,
|
|
667
|
+
endIndex: lines.length - 1,
|
|
668
|
+
text: textLines.map((line) => line.trim()).filter(Boolean).join(' '),
|
|
669
|
+
};
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
const getUnclosedInlineNoteMessage = (noteDeclaration, startLine, lineOffset = 0) => {
|
|
673
|
+
if (noteDeclaration.conflictType === 'note') {
|
|
674
|
+
const conflictLine = lineOffset + noteDeclaration.conflictIndex + 1;
|
|
675
|
+
return `The note starting on line ${startLine} is not closed before another note starts on line ${conflictLine}. Close the first note with "}".`;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
if (noteDeclaration.conflictType === 'reference') {
|
|
679
|
+
const conflictLine = lineOffset + noteDeclaration.conflictIndex + 1;
|
|
680
|
+
return `The note starting on line ${startLine} contains "{note-ref}" on line ${conflictLine}. Close the note before adding a note reference.`;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return `The note starting on line ${startLine} is not closed. End it with "}" on its own line or at the end of the note text.`;
|
|
684
|
+
};
|
|
609
685
|
|
|
610
686
|
/**
|
|
611
687
|
* Finds the deliberately small inline-note syntax while ignoring fenced and
|
|
@@ -640,8 +716,7 @@ export const extractInlineNoteDiagnostics = (markdown, options = {}) => {
|
|
|
640
716
|
pendingParagraph = null;
|
|
641
717
|
};
|
|
642
718
|
|
|
643
|
-
const attachNote = (
|
|
644
|
-
const text = noteMatch[1].trim();
|
|
719
|
+
const attachNote = (text, lineNumber) => {
|
|
645
720
|
if (!text) {
|
|
646
721
|
addError(`The note on line ${lineNumber} is empty. ${getInlineNoteErrorMessage()}`, lineNumber);
|
|
647
722
|
pendingParagraph = null;
|
|
@@ -686,7 +761,10 @@ export const extractInlineNoteDiagnostics = (markdown, options = {}) => {
|
|
|
686
761
|
|
|
687
762
|
const trimmed = line.trim();
|
|
688
763
|
const maskedTrimmed = maskedLine.trim();
|
|
689
|
-
const
|
|
764
|
+
const startsNoteDeclaration = inlineNoteDeclarationOpeningRegex.test(maskedLine);
|
|
765
|
+
const noteDeclaration = startsNoteDeclaration
|
|
766
|
+
? collectInlineNoteDeclaration(lines, maskedLines, index)
|
|
767
|
+
: null;
|
|
690
768
|
const looksLikeNoteDeclaration = inlineNoteDeclarationStartRegex.test(maskedTrimmed);
|
|
691
769
|
const referenceCount = countInlineNoteReferences(maskedLine);
|
|
692
770
|
const isHeading = /^#{1,6}(?:\s|$)/.test(trimmed);
|
|
@@ -698,8 +776,14 @@ export const extractInlineNoteDiagnostics = (markdown, options = {}) => {
|
|
|
698
776
|
}
|
|
699
777
|
|
|
700
778
|
if (pendingParagraph) {
|
|
701
|
-
if (
|
|
702
|
-
|
|
779
|
+
if (noteDeclaration) {
|
|
780
|
+
if (!noteDeclaration.closed) {
|
|
781
|
+
addError(getUnclosedInlineNoteMessage(noteDeclaration, lineNumber, options.lineOffset), lineNumber);
|
|
782
|
+
pendingParagraph = null;
|
|
783
|
+
} else {
|
|
784
|
+
attachNote(noteDeclaration.text, lineNumber);
|
|
785
|
+
}
|
|
786
|
+
index = noteDeclaration.endIndex;
|
|
703
787
|
continue;
|
|
704
788
|
}
|
|
705
789
|
|
|
@@ -716,13 +800,16 @@ export const extractInlineNoteDiagnostics = (markdown, options = {}) => {
|
|
|
716
800
|
noteJustSeen = false;
|
|
717
801
|
}
|
|
718
802
|
|
|
719
|
-
if (
|
|
803
|
+
if (noteDeclaration || looksLikeNoteDeclaration) {
|
|
720
804
|
addError(
|
|
721
|
-
|
|
722
|
-
?
|
|
805
|
+
noteDeclaration
|
|
806
|
+
? noteDeclaration.closed
|
|
807
|
+
? `A "{note: ...}" requires a preceding paragraph containing "{note-ref}".`
|
|
808
|
+
: getUnclosedInlineNoteMessage(noteDeclaration, lineNumber, options.lineOffset)
|
|
723
809
|
: `Invalid note syntax on line ${lineNumber}. ${getInlineNoteErrorMessage()}`,
|
|
724
810
|
lineNumber,
|
|
725
811
|
);
|
|
812
|
+
if (noteDeclaration) index = noteDeclaration.endIndex;
|
|
726
813
|
continue;
|
|
727
814
|
}
|
|
728
815
|
|
|
@@ -1,9 +1,55 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { access, readFile } from 'node:fs/promises';
|
|
2
|
+
import { parseYamlMapping } from './frontmatter-yaml.mjs';
|
|
3
|
+
import {
|
|
4
|
+
splitSiteFile,
|
|
5
|
+
validateConfigFrontmatterStructure,
|
|
6
|
+
validateFrontmatterIndentation,
|
|
7
|
+
} from './site-content.mjs';
|
|
8
|
+
import {
|
|
9
|
+
legacySiteConfigPath,
|
|
10
|
+
siteConfigLabel,
|
|
11
|
+
siteConfigPath,
|
|
12
|
+
siteThemeLabel,
|
|
13
|
+
} from './site-paths.mjs';
|
|
3
14
|
import { readThemeConfig } from './theme-config.mjs';
|
|
4
15
|
import { resolveThemeConfig } from './theme-presets.mjs';
|
|
5
16
|
|
|
6
|
-
const
|
|
17
|
+
const fileExists = (filePath) => access(filePath).then(() => true, () => false);
|
|
18
|
+
const readSiteConfig = async () => {
|
|
19
|
+
const source = await readFile(siteConfigPath, 'utf8').catch(async (error) => {
|
|
20
|
+
if (error?.code === 'ENOENT' && await fileExists(legacySiteConfigPath)) {
|
|
21
|
+
throw new Error(`${siteConfigLabel} is required. Replace the obsolete config.mjs with a frontmatter-only config.md file.`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (error?.code === 'ENOENT') {
|
|
25
|
+
throw new Error(`${siteConfigLabel} is required. Create it before running Norna.`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
throw error;
|
|
29
|
+
});
|
|
30
|
+
const { body, frontmatter, frontmatterBody } = splitSiteFile(source, siteConfigLabel);
|
|
31
|
+
const issues = [];
|
|
32
|
+
|
|
33
|
+
validateFrontmatterIndentation(frontmatter, (issue) => issues.push(issue));
|
|
34
|
+
validateConfigFrontmatterStructure(frontmatter, (issue) => issues.push(issue));
|
|
35
|
+
|
|
36
|
+
if (body.trim()) {
|
|
37
|
+
issues.push({
|
|
38
|
+
message: `${siteConfigLabel} may contain YAML frontmatter only. Remove the Markdown body.`,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (issues.length > 0) {
|
|
43
|
+
throw new Error([
|
|
44
|
+
`${siteConfigLabel} has invalid frontmatter.`,
|
|
45
|
+
...issues.map((issue) => `- ${issue.message}`),
|
|
46
|
+
].join('\n'));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return parseYamlMapping(frontmatterBody);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const siteConfig = await readSiteConfig();
|
|
7
53
|
const themeConfig = await readThemeConfig();
|
|
8
54
|
|
|
9
55
|
const cssLengthPattern = String.raw`(?:\d+|\d*\.\d+)(?:px|rem|em|vw|vh|vmin|vmax|ch|%)`;
|
|
@@ -28,16 +74,6 @@ const readEnum = (object, key, path, allowedValues, fallback, sourceLabel = site
|
|
|
28
74
|
return value;
|
|
29
75
|
};
|
|
30
76
|
|
|
31
|
-
const readString = (object, key, path, sourceLabel = siteConfigLabel) => {
|
|
32
|
-
const value = object[key];
|
|
33
|
-
|
|
34
|
-
if (typeof value !== 'string' || value.trim() === '') {
|
|
35
|
-
throw new Error(`${path}.${key} must be a non-empty string in ${sourceLabel}.`);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return value.trim();
|
|
39
|
-
};
|
|
40
|
-
|
|
41
77
|
const readFontFamily = (object, key, path, fallback, sourceLabel = siteConfigLabel) => {
|
|
42
78
|
const value = object[key] ?? fallback;
|
|
43
79
|
|
|
@@ -138,123 +174,82 @@ const readResponsivePercent = (object, key, path, fallback, sourceLabel = siteCo
|
|
|
138
174
|
});
|
|
139
175
|
};
|
|
140
176
|
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
if (typeof value !== 'boolean') {
|
|
145
|
-
throw new Error(`${path}.${key} must be a boolean in ${siteConfigLabel}.`);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return value;
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const readPositiveInteger = (object, key, path, fallback) => {
|
|
152
|
-
const value = object[key] ?? fallback;
|
|
177
|
+
const readSiteUrl = (config) => {
|
|
178
|
+
const configuredValue = process.env.NORNA_SITE_URL ?? config.url;
|
|
153
179
|
|
|
154
|
-
if (
|
|
155
|
-
throw new Error(
|
|
180
|
+
if (typeof configuredValue !== 'string' || configuredValue.trim() === '') {
|
|
181
|
+
throw new Error(`url must be a non-empty absolute URL in ${siteConfigLabel}.`);
|
|
156
182
|
}
|
|
157
183
|
|
|
158
|
-
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
const readPositiveNumber = (object, key, path, fallback) => {
|
|
162
|
-
const value = object[key] ?? fallback;
|
|
163
|
-
|
|
164
|
-
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
|
|
165
|
-
throw new Error(`${path}.${key} must be a positive number in ${siteConfigLabel}.`);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return value;
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
const readUrl = (object, key, path) => {
|
|
172
|
-
const value = readString(object, key, path);
|
|
173
|
-
|
|
184
|
+
let url;
|
|
174
185
|
try {
|
|
175
|
-
|
|
186
|
+
url = new URL(configuredValue.trim());
|
|
176
187
|
} catch {
|
|
177
|
-
throw new Error(
|
|
188
|
+
throw new Error(`url must be an absolute URL such as "https://example.com/" in ${siteConfigLabel}.`);
|
|
178
189
|
}
|
|
179
|
-
};
|
|
180
190
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (typeof value !== 'string' || value.trim() === '') {
|
|
185
|
-
throw new Error(`${path}.${key} must be a non-empty URL path in ${siteConfigLabel}.`);
|
|
191
|
+
if (!['http:', 'https:'].includes(url.protocol)) {
|
|
192
|
+
throw new Error(`url must use http or https in ${siteConfigLabel}.`);
|
|
186
193
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
) {
|
|
196
|
-
throw new Error(`${path}.${key} must start and end with "/" and must not contain whitespace, "?", "#", or "//" in ${siteConfigLabel}.`);
|
|
194
|
+
if (url.search || url.hash) {
|
|
195
|
+
throw new Error(`url must not contain a query string or fragment in ${siteConfigLabel}.`);
|
|
196
|
+
}
|
|
197
|
+
if (!url.pathname.endsWith('/')) {
|
|
198
|
+
url.pathname = `${url.pathname}/`;
|
|
199
|
+
}
|
|
200
|
+
if (url.pathname.includes('//')) {
|
|
201
|
+
throw new Error(`url must not contain repeated slashes in its path in ${siteConfigLabel}.`);
|
|
197
202
|
}
|
|
198
203
|
|
|
199
|
-
return
|
|
204
|
+
return url;
|
|
200
205
|
};
|
|
201
206
|
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
207
|
+
const localeLabels = Object.freeze({
|
|
208
|
+
en: Object.freeze({
|
|
209
|
+
dismissBanner: 'Dismiss notice',
|
|
210
|
+
gallery: 'Images',
|
|
211
|
+
note: 'Note',
|
|
212
|
+
pageNavigation: 'On this page',
|
|
213
|
+
siteBanners: 'Site notices',
|
|
214
|
+
siteNavigation: 'Pages',
|
|
215
|
+
skipToContent: 'Skip to content',
|
|
216
|
+
}),
|
|
217
|
+
sv: Object.freeze({
|
|
218
|
+
dismissBanner: 'Stäng meddelande',
|
|
219
|
+
gallery: 'Bilder',
|
|
220
|
+
note: 'Not',
|
|
221
|
+
pageNavigation: 'På den här sidan',
|
|
222
|
+
siteBanners: 'Meddelanden',
|
|
223
|
+
siteNavigation: 'Sidor',
|
|
224
|
+
skipToContent: 'Hoppa till innehållet',
|
|
225
|
+
}),
|
|
226
|
+
});
|
|
206
227
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
228
|
+
const readLocale = (config) => {
|
|
229
|
+
const lang = config.language ?? 'en';
|
|
210
230
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
maximumDurationMs,
|
|
215
|
-
minimumDurationMs,
|
|
216
|
-
});
|
|
217
|
-
};
|
|
231
|
+
if (typeof lang !== 'string' || !/^[a-zA-Z]{2,3}(?:-[a-zA-Z0-9]+)*$/.test(lang.trim())) {
|
|
232
|
+
throw new Error(`language must be a valid language tag such as "en" or "sv" in ${siteConfigLabel}.`);
|
|
233
|
+
}
|
|
218
234
|
|
|
219
|
-
const
|
|
220
|
-
const
|
|
221
|
-
const labels =
|
|
222
|
-
const lang = locale.lang ?? 'en';
|
|
235
|
+
const normalizedLang = lang.trim();
|
|
236
|
+
const languageKey = normalizedLang.toLowerCase().split('-')[0];
|
|
237
|
+
const labels = localeLabels[languageKey];
|
|
223
238
|
|
|
224
|
-
if (
|
|
225
|
-
throw new Error(`
|
|
239
|
+
if (!labels) {
|
|
240
|
+
throw new Error(`language "${normalizedLang}" has no built-in Norna UI text. Supported languages: ${Object.keys(localeLabels).join(', ')}.`);
|
|
226
241
|
}
|
|
227
242
|
|
|
228
243
|
return Object.freeze({
|
|
229
|
-
lang:
|
|
230
|
-
labels
|
|
231
|
-
closeMenu: readString({ closeMenu: labels.closeMenu ?? 'Close menu' }, 'closeMenu', 'locale.labels'),
|
|
232
|
-
dismissBanner: readString({ dismissBanner: labels.dismissBanner ?? 'Dismiss notice' }, 'dismissBanner', 'locale.labels'),
|
|
233
|
-
skipToContent: readString({ skipToContent: labels.skipToContent ?? 'Skip to content' }, 'skipToContent', 'locale.labels'),
|
|
234
|
-
sectionNavigation: readString({ sectionNavigation: labels.sectionNavigation ?? 'Sections' }, 'sectionNavigation', 'locale.labels'),
|
|
235
|
-
gallery: readString({ gallery: labels.gallery ?? 'Images' }, 'gallery', 'locale.labels'),
|
|
236
|
-
note: readString({ note: labels.note ?? 'Note' }, 'note', 'locale.labels'),
|
|
237
|
-
menu: readString({ menu: labels.menu ?? 'Menu' }, 'menu', 'locale.labels'),
|
|
238
|
-
pageNavigation: readString({ pageNavigation: labels.pageNavigation ?? 'On this page' }, 'pageNavigation', 'locale.labels'),
|
|
239
|
-
siteNavigation: readString({ siteNavigation: labels.siteNavigation ?? 'Pages' }, 'siteNavigation', 'locale.labels'),
|
|
240
|
-
siteBanners: readString({ siteBanners: labels.siteBanners ?? 'Site notices' }, 'siteBanners', 'locale.labels'),
|
|
241
|
-
}),
|
|
244
|
+
lang: normalizedLang,
|
|
245
|
+
labels,
|
|
242
246
|
});
|
|
243
247
|
};
|
|
244
248
|
|
|
245
|
-
const rawConfig = assertObject(siteConfig, '
|
|
246
|
-
const misplacedThemeKeys = ['layout', 'gallery', 'typography'].filter((key) => key in rawConfig);
|
|
247
|
-
if (misplacedThemeKeys.length > 0) {
|
|
248
|
-
throw new Error(`${misplacedThemeKeys.join(', ')} belong in ${siteThemeLabel}, not ${siteConfigLabel}.`);
|
|
249
|
-
}
|
|
250
|
-
|
|
249
|
+
const rawConfig = assertObject(siteConfig, 'config frontmatter');
|
|
251
250
|
const rawTheme = assertObject(themeConfig, 'theme frontmatter', siteThemeLabel);
|
|
252
|
-
const
|
|
253
|
-
const
|
|
254
|
-
const rawLocale = rawConfig.locale ?? {};
|
|
255
|
-
const rawGithub = assertObject(rawConfig.github, 'github');
|
|
256
|
-
const rawDeploy = assertObject(rawConfig.deploy ?? {}, 'deploy');
|
|
257
|
-
const rawDeployWatch = assertObject(rawDeploy.watch ?? {}, 'deploy.watch');
|
|
251
|
+
const siteUrl = readSiteUrl(rawConfig);
|
|
252
|
+
const scrollBehaviorNames = ['instant', 'browser-smooth'];
|
|
258
253
|
|
|
259
254
|
const defaultFontFamily = "Arial, 'Helvetica Neue', Helvetica, sans-serif";
|
|
260
255
|
const layoutDensityNames = ['compact', 'normal', 'airy'];
|
|
@@ -383,26 +378,20 @@ export const resolveThemeVisualConfig = (theme, sourceLabel = siteThemeLabel) =>
|
|
|
383
378
|
|
|
384
379
|
export const projectConfig = Object.freeze({
|
|
385
380
|
site: Object.freeze({
|
|
386
|
-
basePath:
|
|
387
|
-
url:
|
|
381
|
+
basePath: siteUrl.pathname,
|
|
382
|
+
url: siteUrl.href,
|
|
388
383
|
}),
|
|
389
384
|
...resolveThemeVisualConfig(rawTheme, siteThemeLabel),
|
|
390
385
|
navigation: Object.freeze({
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
}),
|
|
399
|
-
deploy: Object.freeze({
|
|
400
|
-
watch: Object.freeze({
|
|
401
|
-
intervalMs: readPositiveInteger(rawDeployWatch, 'intervalMs', 'deploy.watch', 10_000),
|
|
402
|
-
timeoutMs: readPositiveInteger(rawDeployWatch, 'timeoutMs', 'deploy.watch', 15 * 60_000),
|
|
403
|
-
runLimit: readPositiveInteger(rawDeployWatch, 'runLimit', 'deploy.watch', 10),
|
|
404
|
-
}),
|
|
386
|
+
scrollBehavior: readEnum(
|
|
387
|
+
rawConfig,
|
|
388
|
+
'scrollBehavior',
|
|
389
|
+
'config frontmatter',
|
|
390
|
+
scrollBehaviorNames,
|
|
391
|
+
'instant',
|
|
392
|
+
),
|
|
405
393
|
}),
|
|
394
|
+
locale: readLocale(rawConfig),
|
|
406
395
|
});
|
|
407
396
|
|
|
408
397
|
export default projectConfig;
|
|
@@ -20,6 +20,7 @@ const h2Regex = /^##\s+.*$/gm;
|
|
|
20
20
|
const explicitHeadingIdRegex = /\s*\{#([a-z0-9-]+)\}\s*$/;
|
|
21
21
|
const deprecatedInlineStyleReferenceRegex = /\[[^\]\n]+\]\{\.([a-z][a-z0-9-]*)\}/g;
|
|
22
22
|
const frontmatterDelimiterRegex = /^---\s*$/;
|
|
23
|
+
const knownConfigTopLevelFrontmatterKeys = new Set(['url', 'language', 'scrollBehavior']);
|
|
23
24
|
const knownContentTopLevelFrontmatterKeys = new Set(['title', 'description', 'navigation', 'sections']);
|
|
24
25
|
const knownThemeTopLevelFrontmatterKeys = new Set(['navigation', 'preset', 'layout', 'gallery', 'typography', 'presentation']);
|
|
25
26
|
const knownRouteThemeTopLevelFrontmatterKeys = new Set(['navigation', 'preset', 'layout', 'gallery', 'typography', 'presentation']);
|
|
@@ -270,6 +271,12 @@ export const validateContentFrontmatterStructure = (frontmatter, addIssue) =>
|
|
|
270
271
|
fileKind: 'content',
|
|
271
272
|
});
|
|
272
273
|
|
|
274
|
+
export const validateConfigFrontmatterStructure = (frontmatter, addIssue) =>
|
|
275
|
+
validateFrontmatterStructure(frontmatter, addIssue, {
|
|
276
|
+
knownTopLevelFrontmatterKeys: knownConfigTopLevelFrontmatterKeys,
|
|
277
|
+
fileKind: 'config',
|
|
278
|
+
});
|
|
279
|
+
|
|
273
280
|
export const validateThemeFrontmatterStructure = (frontmatter, addIssue) =>
|
|
274
281
|
validateFrontmatterStructure(frontmatter, addIssue, {
|
|
275
282
|
knownTopLevelFrontmatterKeys: knownThemeTopLevelFrontmatterKeys,
|
|
@@ -22,7 +22,10 @@ export const invocationRoot = configuredInvocationRoot
|
|
|
22
22
|
|
|
23
23
|
const hasSiteFilesInDirectory = (siteDir) => {
|
|
24
24
|
return (
|
|
25
|
-
|
|
25
|
+
(
|
|
26
|
+
existsSync(path.join(siteDir, 'config.md'))
|
|
27
|
+
|| existsSync(path.join(siteDir, 'config.mjs'))
|
|
28
|
+
)
|
|
26
29
|
&& existsSync(path.join(siteDir, 'content.md'))
|
|
27
30
|
);
|
|
28
31
|
};
|
|
@@ -114,7 +117,8 @@ const getPathLabel = (filePath) => {
|
|
|
114
117
|
};
|
|
115
118
|
|
|
116
119
|
export const siteDir = resolvedSitePaths.siteDir;
|
|
117
|
-
export const siteConfigPath = path.join(siteDir, 'config.
|
|
120
|
+
export const siteConfigPath = path.join(siteDir, 'config.md');
|
|
121
|
+
export const legacySiteConfigPath = path.join(siteDir, 'config.mjs');
|
|
118
122
|
export const siteThemePath = path.join(siteDir, 'theme.md');
|
|
119
123
|
export const siteContentPath = path.join(siteDir, 'content.md');
|
|
120
124
|
export const sitewideContentPath = path.join(siteDir, 'sitewide-content.md');
|