@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
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
8
|
+
const nornaBin = path.join(repoRoot, 'bin', 'norna.mjs');
|
|
9
|
+
const tempParent = path.join(repoRoot, 'node_modules', '.cache');
|
|
10
|
+
await mkdir(tempParent, { recursive: true });
|
|
11
|
+
const tempRoot = await mkdtemp(path.join(tempParent, 'norna-client-javascript-'));
|
|
12
|
+
const siteDir = path.join(tempRoot, 'site');
|
|
13
|
+
const routeDir = path.join(siteDir, 'routes', '010-details');
|
|
14
|
+
const configPath = path.join(siteDir, 'config.md');
|
|
15
|
+
|
|
16
|
+
const runBuild = () => {
|
|
17
|
+
const result = spawnSync(process.execPath, [nornaBin, 'build'], {
|
|
18
|
+
cwd: tempRoot,
|
|
19
|
+
encoding: 'utf8',
|
|
20
|
+
env: {
|
|
21
|
+
...process.env,
|
|
22
|
+
NORNA_SITE_DIR: siteDir,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (result.status !== 0) {
|
|
27
|
+
throw new Error([
|
|
28
|
+
`norna build exited with code ${result.status}.`,
|
|
29
|
+
result.stdout.trim(),
|
|
30
|
+
result.stderr.trim(),
|
|
31
|
+
].filter(Boolean).join('\n'));
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const readPage = (pathname = 'index.html') => readFile(path.join(tempRoot, 'dist', pathname), 'utf8');
|
|
36
|
+
const getScripts = (html) => html.match(/<script\b[^>]*>[\s\S]*?<\/script>/gi) ?? [];
|
|
37
|
+
const assertNoClientJavaScript = (html, label) => {
|
|
38
|
+
assert.deepEqual(getScripts(html), [], `${label} should not require client-side JavaScript.`);
|
|
39
|
+
};
|
|
40
|
+
const assertScrollBehavior = (html, behavior, label) => {
|
|
41
|
+
assert.match(
|
|
42
|
+
html,
|
|
43
|
+
new RegExp(`<html\\b[^>]*style="[^"]*scroll-behavior: ${behavior}(?:;|"|\")`),
|
|
44
|
+
`${label} should render scroll-behavior: ${behavior}.`,
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
const writeConfig = (scrollBehavior) => writeFile(configPath, `---
|
|
48
|
+
url: https://example.com/
|
|
49
|
+
${scrollBehavior ? `scrollBehavior: ${scrollBehavior}\n` : ''}---
|
|
50
|
+
`);
|
|
51
|
+
const writeSvg = (filePath, color) => writeFile(filePath, [
|
|
52
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 90">',
|
|
53
|
+
` <rect width="160" height="90" fill="${color}"/>`,
|
|
54
|
+
'</svg>',
|
|
55
|
+
'',
|
|
56
|
+
].join('\n'));
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
await mkdir(path.join(siteDir, 'public'), { recursive: true });
|
|
60
|
+
await writeConfig();
|
|
61
|
+
await writeFile(path.join(siteDir, 'theme.md'), `---
|
|
62
|
+
typography:
|
|
63
|
+
preset: text-forward
|
|
64
|
+
presentation:
|
|
65
|
+
palette: paper
|
|
66
|
+
---
|
|
67
|
+
`);
|
|
68
|
+
await writeFile(path.join(siteDir, 'sitewide-content.md'), `---
|
|
69
|
+
navigation:
|
|
70
|
+
brand: Test site
|
|
71
|
+
---
|
|
72
|
+
`);
|
|
73
|
+
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
74
|
+
title: Static single page
|
|
75
|
+
description: A single page without interactive features.
|
|
76
|
+
sections:
|
|
77
|
+
intro: {}
|
|
78
|
+
more: {}
|
|
79
|
+
---
|
|
80
|
+
## Intro {#intro}
|
|
81
|
+
|
|
82
|
+
Ordinary Markdown needs no client-side JavaScript.
|
|
83
|
+
|
|
84
|
+
## More {#more}
|
|
85
|
+
|
|
86
|
+
Additional sections do not make the page interactive.
|
|
87
|
+
`);
|
|
88
|
+
|
|
89
|
+
runBuild();
|
|
90
|
+
const instantHtml = await readPage();
|
|
91
|
+
assertNoClientJavaScript(instantHtml, 'The default instant scroll mode');
|
|
92
|
+
assertScrollBehavior(instantHtml, 'auto', 'The default instant scroll mode');
|
|
93
|
+
assert.match(instantHtml, /<nav class="page-nav"/);
|
|
94
|
+
assert.match(instantHtml, /href="#intro"/);
|
|
95
|
+
assert.match(instantHtml, /href="#more"/);
|
|
96
|
+
|
|
97
|
+
await writeConfig('browser-smooth');
|
|
98
|
+
runBuild();
|
|
99
|
+
const browserSmoothHtml = await readPage();
|
|
100
|
+
assertNoClientJavaScript(browserSmoothHtml, 'The browser smooth scroll mode');
|
|
101
|
+
assertScrollBehavior(browserSmoothHtml, 'smooth', 'The browser smooth scroll mode');
|
|
102
|
+
|
|
103
|
+
await writeConfig();
|
|
104
|
+
|
|
105
|
+
await mkdir(path.join(routeDir, 'images', 'details'), { recursive: true });
|
|
106
|
+
await writeSvg(path.join(routeDir, 'images', 'details', 'first.svg'), '#7b8f78');
|
|
107
|
+
await writeSvg(path.join(routeDir, 'images', 'details', 'second.svg'), '#a7664b');
|
|
108
|
+
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
109
|
+
title: Static baseline
|
|
110
|
+
description: A page without interactive features.
|
|
111
|
+
sections:
|
|
112
|
+
intro: {}
|
|
113
|
+
---
|
|
114
|
+
## Intro {#intro}
|
|
115
|
+
|
|
116
|
+
Ordinary Markdown needs no client-side JavaScript.
|
|
117
|
+
`);
|
|
118
|
+
await writeFile(path.join(routeDir, 'route-content.md'), `---
|
|
119
|
+
title: Details
|
|
120
|
+
description: Static managed images.
|
|
121
|
+
sections:
|
|
122
|
+
details: {}
|
|
123
|
+
---
|
|
124
|
+
## Details {#details}
|
|
125
|
+
|
|
126
|
+
Static image stacks remain static.
|
|
127
|
+
|
|
128
|
+
\`\`\`norna-image-stack
|
|
129
|
+
- image: first.svg
|
|
130
|
+
caption: First image.
|
|
131
|
+
- image: second.svg
|
|
132
|
+
caption: Second image.
|
|
133
|
+
\`\`\`
|
|
134
|
+
|
|
135
|
+
\`\`\`norna-card-list
|
|
136
|
+
- title: Static card
|
|
137
|
+
text: Cards do not require client-side behaviour.
|
|
138
|
+
\`\`\`
|
|
139
|
+
`);
|
|
140
|
+
|
|
141
|
+
runBuild();
|
|
142
|
+
assertNoClientJavaScript(await readPage(), 'A plain homepage in a multi-page site');
|
|
143
|
+
assertNoClientJavaScript(
|
|
144
|
+
await readPage(path.join('details', 'index.html')),
|
|
145
|
+
'A page with managed image stacks and cards',
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
149
|
+
title: Notes
|
|
150
|
+
description: A page with an enhanced sidenote.
|
|
151
|
+
sections:
|
|
152
|
+
intro: {}
|
|
153
|
+
---
|
|
154
|
+
## Intro {#intro}
|
|
155
|
+
|
|
156
|
+
This sentence has additional context.{note-ref}
|
|
157
|
+
|
|
158
|
+
{note: The note remains readable without JavaScript, while alignment is enhanced when JavaScript runs.}
|
|
159
|
+
`);
|
|
160
|
+
await writeFile(path.join(routeDir, 'route-content.md'), `---
|
|
161
|
+
title: Carousel
|
|
162
|
+
description: An interactive image carousel.
|
|
163
|
+
sections:
|
|
164
|
+
details: {}
|
|
165
|
+
---
|
|
166
|
+
## Details {#details}
|
|
167
|
+
|
|
168
|
+
\`\`\`norna-image-carousel
|
|
169
|
+
- image: first.svg
|
|
170
|
+
caption: First slide.
|
|
171
|
+
- image: second.svg
|
|
172
|
+
caption: Second slide.
|
|
173
|
+
\`\`\`
|
|
174
|
+
`);
|
|
175
|
+
|
|
176
|
+
runBuild();
|
|
177
|
+
const noteHtml = await readPage();
|
|
178
|
+
const carouselHtml = await readPage(path.join('details', 'index.html'));
|
|
179
|
+
assert.equal(getScripts(noteHtml).length, 1, 'A page with notes should load only the note enhancement.');
|
|
180
|
+
assert.match(noteHtml, /data-note-id=/);
|
|
181
|
+
assert.equal(getScripts(carouselHtml).length, 1, 'A carousel page should load only the carousel implementation.');
|
|
182
|
+
assert.match(carouselHtml, /data-carousel/);
|
|
183
|
+
assert.match(getScripts(carouselHtml)[0], /\ssrc=/, 'Carousel JavaScript should be emitted as a module asset.');
|
|
184
|
+
|
|
185
|
+
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
186
|
+
title: Banner
|
|
187
|
+
description: A page with a dismissible banner.
|
|
188
|
+
sections:
|
|
189
|
+
intro: {}
|
|
190
|
+
---
|
|
191
|
+
## Intro {#intro}
|
|
192
|
+
|
|
193
|
+
The only interactive feature is the banner.
|
|
194
|
+
`);
|
|
195
|
+
await writeFile(path.join(routeDir, 'route-content.md'), `---
|
|
196
|
+
title: Details
|
|
197
|
+
description: A second plain page.
|
|
198
|
+
sections:
|
|
199
|
+
details: {}
|
|
200
|
+
---
|
|
201
|
+
## Details {#details}
|
|
202
|
+
|
|
203
|
+
Plain route content.
|
|
204
|
+
`);
|
|
205
|
+
await writeFile(path.join(siteDir, 'sitewide-content.md'), `---
|
|
206
|
+
banners:
|
|
207
|
+
- id: test-banner
|
|
208
|
+
title: Test banner
|
|
209
|
+
text: This banner can be dismissed.
|
|
210
|
+
---
|
|
211
|
+
`);
|
|
212
|
+
|
|
213
|
+
runBuild();
|
|
214
|
+
const bannerHtml = await readPage();
|
|
215
|
+
assert.equal(getScripts(bannerHtml).length, 1, 'A dismissible banner should load only its dismissal script.');
|
|
216
|
+
assert.match(getScripts(bannerHtml)[0], /norna-banner:/);
|
|
217
|
+
|
|
218
|
+
console.log('Client JavaScript boundaries test passed.');
|
|
219
|
+
} finally {
|
|
220
|
+
await rm(tempRoot, { force: true, recursive: true });
|
|
221
|
+
}
|
|
@@ -239,6 +239,109 @@ test('content:check accepts valid inline notes and ignores note syntax in code f
|
|
|
239
239
|
});
|
|
240
240
|
});
|
|
241
241
|
|
|
242
|
+
test('content:check accepts wrapped and explicit multiline inline notes', async () => {
|
|
243
|
+
await withTempProject({
|
|
244
|
+
site: `---
|
|
245
|
+
title: Multiline inline notes
|
|
246
|
+
description: Fixture
|
|
247
|
+
---
|
|
248
|
+
## Wrapped {#wrapped}
|
|
249
|
+
This paragraph has a formatter-wrapped note.{note-ref}
|
|
250
|
+
|
|
251
|
+
{note: This note was wrapped by
|
|
252
|
+
an editor before its closing brace.}
|
|
253
|
+
|
|
254
|
+
## Explicit {#explicit}
|
|
255
|
+
This paragraph has an explicit multiline note.{note-ref}
|
|
256
|
+
|
|
257
|
+
{note:
|
|
258
|
+
This note uses the explicit
|
|
259
|
+
multiline form.
|
|
260
|
+
}
|
|
261
|
+
`,
|
|
262
|
+
files: [],
|
|
263
|
+
}, async (root) => {
|
|
264
|
+
const result = runContentScript(root, ['--check']);
|
|
265
|
+
const output = getOutput(result);
|
|
266
|
+
|
|
267
|
+
assert.equal(result.status, 0, output);
|
|
268
|
+
assert.match(output, /Content check passed\./);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('content:check reports an unclosed multiline inline note', async () => {
|
|
273
|
+
await withTempProject({
|
|
274
|
+
site: `---
|
|
275
|
+
title: Unclosed inline note
|
|
276
|
+
description: Fixture
|
|
277
|
+
---
|
|
278
|
+
## Intro {#intro}
|
|
279
|
+
This paragraph has a note reference.{note-ref}
|
|
280
|
+
|
|
281
|
+
{note: This note never closes
|
|
282
|
+
and continues to the end of the file.
|
|
283
|
+
`,
|
|
284
|
+
files: [],
|
|
285
|
+
}, async (root) => {
|
|
286
|
+
const result = runContentScript(root, ['--check']);
|
|
287
|
+
const output = getOutput(result);
|
|
288
|
+
|
|
289
|
+
assert.equal(result.status, 1, output);
|
|
290
|
+
assert.match(output, /The note starting on line \d+ is not closed\./);
|
|
291
|
+
assert.match(output, /End it with "\}" on its own line or at the end of the note text\./);
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test('content:check rejects nested note syntax before a multiline note is closed', async () => {
|
|
296
|
+
await withTempProject({
|
|
297
|
+
site: `---
|
|
298
|
+
title: Nested inline note syntax
|
|
299
|
+
description: Fixture
|
|
300
|
+
---
|
|
301
|
+
## Nested note {#nested-note}
|
|
302
|
+
This paragraph has a note reference.{note-ref}
|
|
303
|
+
|
|
304
|
+
{note: The first note is still open
|
|
305
|
+
{note: A second note starts here.}
|
|
306
|
+
|
|
307
|
+
## Nested reference {#nested-reference}
|
|
308
|
+
This paragraph has another note reference.{note-ref}
|
|
309
|
+
|
|
310
|
+
{note: This note is still open
|
|
311
|
+
and contains {note-ref} before closing.}
|
|
312
|
+
`,
|
|
313
|
+
files: [],
|
|
314
|
+
}, async (root) => {
|
|
315
|
+
const result = runContentScript(root, ['--check']);
|
|
316
|
+
const output = getOutput(result);
|
|
317
|
+
|
|
318
|
+
assert.equal(result.status, 1, output);
|
|
319
|
+
assert.match(output, /is not closed before another note starts on line \d+\./);
|
|
320
|
+
assert.match(output, /contains "\{note-ref\}" on line \d+\./);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test('content:check allows escaped and inline-code note syntax inside notes', async () => {
|
|
325
|
+
await withTempProject({
|
|
326
|
+
site: `---
|
|
327
|
+
title: Literal inline note syntax
|
|
328
|
+
description: Fixture
|
|
329
|
+
---
|
|
330
|
+
## Intro {#intro}
|
|
331
|
+
This paragraph has a note reference.{note-ref}
|
|
332
|
+
|
|
333
|
+
{note: Write \\{note: ... to show escaped text, or use \`{note-ref}\` as inline code.}
|
|
334
|
+
`,
|
|
335
|
+
files: [],
|
|
336
|
+
}, async (root) => {
|
|
337
|
+
const result = runContentScript(root, ['--check']);
|
|
338
|
+
const output = getOutput(result);
|
|
339
|
+
|
|
340
|
+
assert.equal(result.status, 0, output);
|
|
341
|
+
assert.match(output, /Content check passed\./);
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
242
345
|
test('content:check rejects unpaired and repeated inline notes', async () => {
|
|
243
346
|
await withTempProject({
|
|
244
347
|
site: `---
|
|
@@ -51,17 +51,9 @@ const createTempSite = async ({ underRepoCache = false } = {}) => {
|
|
|
51
51
|
const root = await mkdtemp(path.join(tempParent, 'norna-content-model-v2-'));
|
|
52
52
|
const siteDir = path.join(root, 'site');
|
|
53
53
|
await mkdir(siteDir, { recursive: true });
|
|
54
|
-
await writeFile(path.join(siteDir, 'config.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
basePath: '/',
|
|
58
|
-
},
|
|
59
|
-
github: {
|
|
60
|
-
branch: 'main',
|
|
61
|
-
repo: 'owner/repo',
|
|
62
|
-
pagesWorkflow: 'Deploy to GitHub Pages',
|
|
63
|
-
},
|
|
64
|
-
};
|
|
54
|
+
await writeFile(path.join(siteDir, 'config.md'), `---
|
|
55
|
+
url: https://example.com/
|
|
56
|
+
---
|
|
65
57
|
`);
|
|
66
58
|
await writeFile(path.join(siteDir, 'theme.md'), `---
|
|
67
59
|
typography:
|
|
@@ -294,11 +286,15 @@ description: Fixture
|
|
|
294
286
|
|
|
295
287
|
The first paragraph points to an explanation.{note-ref}
|
|
296
288
|
|
|
297
|
-
{note: This explanation names \`sitewide-content.md\` and stays beside the
|
|
289
|
+
{note: This explanation names \`sitewide-content.md\` and stays beside the
|
|
290
|
+
paragraph on wide screens.}
|
|
298
291
|
|
|
299
292
|
The second paragraph has its own explanation.{note-ref}
|
|
300
293
|
|
|
301
|
-
{note:
|
|
294
|
+
{note:
|
|
295
|
+
This is the second
|
|
296
|
+
explanation.
|
|
297
|
+
}
|
|
302
298
|
`);
|
|
303
299
|
|
|
304
300
|
await runNorna(['--site-dir', siteDir, 'build']);
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { mkdir, mkdtemp, readFile, readdir, rm, symlink, writeFile } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
const repoRoot = path.resolve(import.meta.dirname, '..');
|
|
8
|
+
const cliPath = path.join(repoRoot, 'bin', 'norna.mjs');
|
|
9
|
+
const tutorialPath = path.join(repoRoot, 'docs', 'getting-started.md');
|
|
10
|
+
const scratchRoot = path.join(repoRoot, 'todos');
|
|
11
|
+
await mkdir(scratchRoot, { recursive: true });
|
|
12
|
+
const tempRoot = await mkdtemp(path.join(scratchRoot, 'norna-documentation-'));
|
|
13
|
+
const tutorialSiteRoot = path.join(tempRoot, 'my-site');
|
|
14
|
+
|
|
15
|
+
const runCli = (args) => spawnSync(process.execPath, [cliPath, ...args], {
|
|
16
|
+
cwd: repoRoot,
|
|
17
|
+
encoding: 'utf8',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const assertSuccess = (result, label) => {
|
|
21
|
+
assert.equal(result.status, 0, `${label} failed:\n${result.stderr || result.stdout}`);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const extractTutorialFile = (source, label, language) => {
|
|
25
|
+
const escapedLabel = label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
26
|
+
const pattern = new RegExp(`${escapedLabel}\\n\\n\\x60{3}${language}\\n([\\s\\S]*?)\\n\\x60{3}`);
|
|
27
|
+
const match = source.match(pattern);
|
|
28
|
+
assert.ok(match, `Could not find the ${label} code block in docs/getting-started.md.`);
|
|
29
|
+
return `${match[1]}\n`;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const collectMarkdownFiles = async (directory) => {
|
|
33
|
+
const files = [];
|
|
34
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
35
|
+
const entryPath = path.join(directory, entry.name);
|
|
36
|
+
if (entry.isDirectory()) {
|
|
37
|
+
files.push(...await collectMarkdownFiles(entryPath));
|
|
38
|
+
} else if (entry.name.endsWith('.md')) {
|
|
39
|
+
files.push(entryPath);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return files;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const checkLocalMarkdownLinks = async () => {
|
|
46
|
+
const markdownFiles = [
|
|
47
|
+
path.join(repoRoot, 'README.md'),
|
|
48
|
+
...await collectMarkdownFiles(path.join(repoRoot, 'docs')),
|
|
49
|
+
...await collectMarkdownFiles(path.join(repoRoot, 'examples')),
|
|
50
|
+
];
|
|
51
|
+
const missing = [];
|
|
52
|
+
|
|
53
|
+
for (const markdownPath of markdownFiles) {
|
|
54
|
+
const source = await readFile(markdownPath, 'utf8');
|
|
55
|
+
const prose = source
|
|
56
|
+
.replace(/(```+|~~~+)[\s\S]*?\1/g, '')
|
|
57
|
+
.replace(/`[^`\n]*`/g, '');
|
|
58
|
+
for (const match of prose.matchAll(/!?\[[^\]]*\]\(([^)]+)\)/g)) {
|
|
59
|
+
const target = match[1].trim().replace(/^<|>$/g, '');
|
|
60
|
+
if (/^(?:[a-z]+:|#|\/)/i.test(target)) continue;
|
|
61
|
+
|
|
62
|
+
const targetPath = decodeURIComponent(target.split('#', 1)[0].split('?', 1)[0]);
|
|
63
|
+
if (!targetPath) continue;
|
|
64
|
+
const resolved = path.resolve(path.dirname(markdownPath), targetPath);
|
|
65
|
+
if (!existsSync(resolved)) {
|
|
66
|
+
missing.push(`${path.relative(repoRoot, markdownPath)} -> ${target}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
assert.deepEqual(missing, [], `Broken local Markdown links:\n${missing.join('\n')}`);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const tutorial = await readFile(tutorialPath, 'utf8');
|
|
76
|
+
const initResult = runCli(['init', tutorialSiteRoot]);
|
|
77
|
+
assertSuccess(initResult, 'Tutorial init');
|
|
78
|
+
await rm(path.join(tutorialSiteRoot, 'node_modules'), { force: true, recursive: true });
|
|
79
|
+
await symlink(path.join(repoRoot, 'node_modules'), path.join(tutorialSiteRoot, 'node_modules'), 'dir');
|
|
80
|
+
|
|
81
|
+
await Promise.all([
|
|
82
|
+
writeFile(
|
|
83
|
+
path.join(tutorialSiteRoot, 'site', 'content.md'),
|
|
84
|
+
extractTutorialFile(tutorial, 'Replace `site/content.md` with:', 'md'),
|
|
85
|
+
),
|
|
86
|
+
writeFile(
|
|
87
|
+
path.join(tutorialSiteRoot, 'site', 'sitewide-content.md'),
|
|
88
|
+
extractTutorialFile(tutorial, 'Replace `site/sitewide-content.md` with:', 'yaml'),
|
|
89
|
+
),
|
|
90
|
+
writeFile(
|
|
91
|
+
path.join(tutorialSiteRoot, 'site', 'theme.md'),
|
|
92
|
+
extractTutorialFile(tutorial, 'Replace `site/theme.md` with:', 'yaml'),
|
|
93
|
+
),
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
const siteDirectory = path.join(tutorialSiteRoot, 'site');
|
|
97
|
+
assertSuccess(runCli(['--site-dir', siteDirectory, 'config:check']), 'Tutorial config:check');
|
|
98
|
+
assertSuccess(runCli(['--site-dir', siteDirectory, 'content:check']), 'Tutorial content:check');
|
|
99
|
+
assertSuccess(runCli(['--site-dir', siteDirectory, 'build']), 'Tutorial build');
|
|
100
|
+
|
|
101
|
+
const html = await readFile(path.join(tutorialSiteRoot, 'dist', 'index.html'), 'utf8');
|
|
102
|
+
assert.match(html, /My first Norna site/);
|
|
103
|
+
assert.match(html, /Welcome/);
|
|
104
|
+
assert.match(html, /Next/);
|
|
105
|
+
|
|
106
|
+
await checkLocalMarkdownLinks();
|
|
107
|
+
|
|
108
|
+
const llms = await readFile(path.join(repoRoot, 'site', 'public', 'llms.txt'), 'utf8');
|
|
109
|
+
for (const match of llms.matchAll(/https:\/\/raw\.githubusercontent\.com\/janga\/norna\/main\/([^\s)]+)/g)) {
|
|
110
|
+
assert.ok(existsSync(path.join(repoRoot, decodeURIComponent(match[1]))), `llms.txt target is missing: ${match[1]}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log('ok - tutorial source builds and documentation links resolve');
|
|
114
|
+
} finally {
|
|
115
|
+
await rm(tempRoot, { force: true, recursive: true });
|
|
116
|
+
}
|
|
@@ -24,9 +24,18 @@ try {
|
|
|
24
24
|
assert.match(versionResult.stdout, new RegExp(`Installed norna: ${packageJson.version.replaceAll('.', '\\.')}`));
|
|
25
25
|
assert.match(versionResult.stdout, /Installed Astro: /);
|
|
26
26
|
|
|
27
|
+
const deployHelpResult = runCli(['deploy', '--help']);
|
|
28
|
+
assert.equal(deployHelpResult.status, 0, deployHelpResult.stderr || deployHelpResult.stdout);
|
|
29
|
+
assert.match(deployHelpResult.stdout, /already committed default branch/);
|
|
30
|
+
const deployWatchHelpResult = runCli(['deploy:watch', '--help']);
|
|
31
|
+
assert.equal(deployWatchHelpResult.status, 0, deployWatchHelpResult.stderr || deployWatchHelpResult.stdout);
|
|
32
|
+
assert.match(deployWatchHelpResult.stdout, /Default: current repository/);
|
|
33
|
+
assert.match(deployWatchHelpResult.stdout, /Default: repository default branch/);
|
|
34
|
+
assert.match(deployWatchHelpResult.stdout, /Default: deploy\.yml/);
|
|
35
|
+
|
|
27
36
|
const directSiteRoot = path.join(tempRoot, 'current-directory-site');
|
|
28
37
|
await mkdir(directSiteRoot, { recursive: true });
|
|
29
|
-
await writeFile(path.join(directSiteRoot, 'config.
|
|
38
|
+
await writeFile(path.join(directSiteRoot, 'config.md'), '---\nurl: https://example.com/\n---\n');
|
|
30
39
|
await writeFile(path.join(directSiteRoot, 'content.md'), '---\ntitle: Direct Site\ndescription: Direct site fixture.\n---\n\n## Intro {#intro}\n\nText.\n');
|
|
31
40
|
const directSiteProjectRoot = await realpath(tempRoot);
|
|
32
41
|
const directSiteDoctorResult = runCli(['doctor'], {
|
|
@@ -95,7 +104,7 @@ try {
|
|
|
95
104
|
assert.equal(customPurePackageJson.scripts['norna:dev'], 'norna --site-dir presentation dev:local');
|
|
96
105
|
assert.equal(customPurePackageJson.scripts['norna:build'], 'norna --site-dir presentation build');
|
|
97
106
|
await readFile(path.join(customPureSiteRoot, 'presentation', 'content.md'));
|
|
98
|
-
await readFile(path.join(customPureSiteRoot, 'presentation', 'config.
|
|
107
|
+
await readFile(path.join(customPureSiteRoot, 'presentation', 'config.md'));
|
|
99
108
|
await readFile(path.join(customPureSiteRoot, 'presentation', 'theme.md'));
|
|
100
109
|
|
|
101
110
|
const mixedProjectRoot = path.join(tempRoot, 'mixed-project');
|
|
@@ -121,7 +130,7 @@ try {
|
|
|
121
130
|
assert.equal(mixedPackageJson.scripts['norna:engine:update'], 'norna --site-dir presentation engine:update');
|
|
122
131
|
assert.equal(mixedPackageJson.scripts['norna:engine:version'], 'norna --site-dir presentation engine:version');
|
|
123
132
|
await readFile(path.join(mixedProjectRoot, 'presentation', 'content.md'));
|
|
124
|
-
await readFile(path.join(mixedProjectRoot, 'presentation', 'config.
|
|
133
|
+
await readFile(path.join(mixedProjectRoot, 'presentation', 'config.md'));
|
|
125
134
|
await readFile(path.join(mixedProjectRoot, 'presentation', 'theme.md'));
|
|
126
135
|
|
|
127
136
|
const conflictProjectRoot = path.join(tempRoot, 'conflict-project');
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
1
3
|
import path from 'node:path';
|
|
2
4
|
import { fileURLToPath } from 'node:url';
|
|
3
5
|
import { getExampleSites } from './lib/example-sites.mjs';
|
|
@@ -10,6 +12,13 @@ const examples = await getExampleSites(root);
|
|
|
10
12
|
for (const example of examples) {
|
|
11
13
|
console.log(`\nBuilding ${example.siteLabel}`);
|
|
12
14
|
await runInherit(process.execPath, [cliPath, '--site-dir', example.siteLabel, 'build'], { cwd: root });
|
|
15
|
+
|
|
16
|
+
if (['svg', 'png', 'jpg', 'jpeg'].some((extension) => existsSync(path.join(example.siteDirectory, 'public', `logo.${extension}`)))) {
|
|
17
|
+
const homepageHtml = await readFile(path.join(root, 'dist', 'index.html'), 'utf8');
|
|
18
|
+
if (!homepageHtml.includes('class="site-brand-logo"')) {
|
|
19
|
+
throw new Error(`Example ${example.siteLabel} has a logo file that is not rendered on its homepage.`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
13
22
|
}
|
|
14
23
|
|
|
15
24
|
console.log(`\nok - built ${examples.length} example sites`);
|
|
@@ -158,12 +158,13 @@ try {
|
|
|
158
158
|
assertFileExists(path.join(packagedStarterRoot, '.github', 'workflows', 'deploy.yml')),
|
|
159
159
|
assertFileExists(path.join(packagedStarterRoot, 'package.json')),
|
|
160
160
|
assertFileExists(path.join(packagedStarterRoot, 'README.md')),
|
|
161
|
-
assertFileExists(path.join(packagedStarterRoot, 'site', 'config.
|
|
161
|
+
assertFileExists(path.join(packagedStarterRoot, 'site', 'config.md')),
|
|
162
162
|
assertFileExists(path.join(packagedStarterRoot, 'site', 'theme.md')),
|
|
163
163
|
assertFileExists(path.join(packagedStarterRoot, 'site', 'sitewide-content.md')),
|
|
164
164
|
assertFileExists(path.join(packagedStarterRoot, 'site', 'content.md')),
|
|
165
165
|
assertFileExists(path.join(packagedStarterRoot, 'site', 'images', 'work', '.gitkeep')),
|
|
166
166
|
assertFileExists(path.join(packagedStarterRoot, 'site', 'public', 'robots.txt')),
|
|
167
|
+
assertFileMissing(path.join(packagedStarterRoot, 'site', 'config.mjs')),
|
|
167
168
|
]);
|
|
168
169
|
await assertFileIncludes(
|
|
169
170
|
path.join(packagedStarterRoot, '.github', 'workflows', 'deploy.yml'),
|
|
@@ -177,13 +178,11 @@ try {
|
|
|
177
178
|
packageJson.name = 'norna-package-check-site';
|
|
178
179
|
packageJson.dependencies['@janga/norna'] = tarballPath;
|
|
179
180
|
await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
|
|
180
|
-
const packageCheckConfigPath = path.join(siteProjectRoot, 'site', 'config.
|
|
181
|
+
const packageCheckConfigPath = path.join(siteProjectRoot, 'site', 'config.md');
|
|
181
182
|
const packageCheckConfig = await readFile(packageCheckConfigPath, 'utf8');
|
|
182
183
|
await writeFile(
|
|
183
184
|
packageCheckConfigPath,
|
|
184
|
-
packageCheckConfig
|
|
185
|
-
.replace("url: 'https://example.com/'", "url: 'https://example.com/site/'")
|
|
186
|
-
.replace("basePath: '/'", "basePath: '/site/'"),
|
|
185
|
+
packageCheckConfig.replace('url: https://example.com/', 'url: https://example.com/site/'),
|
|
187
186
|
);
|
|
188
187
|
const packageCheckSitewidePath = path.join(siteProjectRoot, 'site', 'sitewide-content.md');
|
|
189
188
|
const packageCheckSitewide = await readFile(packageCheckSitewidePath, 'utf8');
|
|
@@ -241,10 +240,11 @@ This route verifies that packaged norna sites can build route pages.
|
|
|
241
240
|
await runInherit(nornaBinPath, ['init', initializedSiteRoot], { cwd: tempRoot, env: npmEnv });
|
|
242
241
|
await Promise.all([
|
|
243
242
|
assertFileExists(path.join(initializedSiteRoot, 'package.json')),
|
|
244
|
-
assertFileExists(path.join(initializedSiteRoot, 'site', 'config.
|
|
243
|
+
assertFileExists(path.join(initializedSiteRoot, 'site', 'config.md')),
|
|
245
244
|
assertFileExists(path.join(initializedSiteRoot, 'site', 'theme.md')),
|
|
246
245
|
assertFileMissing(path.join(initializedSiteRoot, '.DS_Store')),
|
|
247
246
|
assertFileMissing(path.join(initializedSiteRoot, 'site', '.DS_Store')),
|
|
247
|
+
assertFileMissing(path.join(initializedSiteRoot, 'site', 'config.mjs')),
|
|
248
248
|
]);
|
|
249
249
|
await runInherit(npxBin, ['norna', 'engine:version'], { cwd: path.join(siteProjectRoot, 'site', 'images', 'work'), env: npmEnv });
|
|
250
250
|
await runInherit(npxBin, ['norna', 'doctor'], { cwd: path.join(siteProjectRoot, 'site', 'images', 'work'), env: npmEnv });
|
|
@@ -287,6 +287,14 @@ This route verifies that packaged norna sites can build route pages.
|
|
|
287
287
|
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
288
288
|
'<meta name="format-detection" content="telephone=no">',
|
|
289
289
|
);
|
|
290
|
+
await assertFileIncludes(
|
|
291
|
+
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
292
|
+
'style="scroll-behavior: auto;',
|
|
293
|
+
);
|
|
294
|
+
await assertFileExcludes(
|
|
295
|
+
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
296
|
+
'data-smooth-scroll=',
|
|
297
|
+
);
|
|
290
298
|
await assertFileIncludes(
|
|
291
299
|
path.join(siteProjectRoot, 'dist', 'about', 'index.html'),
|
|
292
300
|
'<link rel="canonical" href="https://example.com/site/about/">',
|
|
@@ -506,17 +514,17 @@ This route verifies that packaged norna sites can build route pages.
|
|
|
506
514
|
'typography.fontFamily',
|
|
507
515
|
);
|
|
508
516
|
await writeFile(siteThemePath, siteTheme);
|
|
509
|
-
const siteConfigPath = path.join(siteProjectRoot, 'site', 'config.
|
|
517
|
+
const siteConfigPath = path.join(siteProjectRoot, 'site', 'config.md');
|
|
510
518
|
const siteConfig = await readFile(siteConfigPath, 'utf8');
|
|
511
519
|
await writeFile(
|
|
512
520
|
siteConfigPath,
|
|
513
|
-
siteConfig.replace(
|
|
521
|
+
siteConfig.replace('url: https://example.com/site/', 'url: example.com/site/'),
|
|
514
522
|
);
|
|
515
523
|
await runExpectFailure(
|
|
516
524
|
npxBin,
|
|
517
525
|
['norna', 'config:check'],
|
|
518
526
|
{ cwd: siteProjectRoot, env: npmEnv },
|
|
519
|
-
'
|
|
527
|
+
'url must be an absolute URL',
|
|
520
528
|
);
|
|
521
529
|
|
|
522
530
|
console.log('Package check passed.');
|