@janga/norna 0.7.1 → 0.7.3
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 +32 -18
- package/astro.config.mjs +2 -0
- package/bin/norna-cli.mjs +175 -0
- package/bin/norna.mjs +149 -150
- package/docs/README.md +41 -22
- package/docs/commands.md +25 -12
- package/docs/configuration.md +38 -135
- package/docs/content.md +108 -265
- package/docs/{command-organization.md → design/command-organization.md} +64 -60
- package/docs/{site-examples-structure-note.md → design/site-examples-structure.md} +19 -24
- package/docs/engine-development.md +35 -7
- package/docs/getting-started.md +55 -26
- package/docs/images-and-metadata.md +1 -1
- package/docs/local-development.md +14 -1
- package/docs/publishing.md +24 -1
- package/docs/routes.md +90 -0
- package/docs/site-structure.md +18 -10
- package/docs/theme.md +292 -0
- package/docs/typography.md +144 -0
- package/examples/dog-gallery/.astro/collections/site.schema.json +802 -0
- package/examples/dog-gallery/.astro/collections/theme.schema.json +714 -0
- package/examples/dog-gallery/.astro/content-assets.mjs +4 -0
- package/examples/dog-gallery/.astro/content-modules.mjs +1 -0
- package/examples/dog-gallery/.astro/content.d.ts +175 -0
- package/examples/dog-gallery/.astro/data-store.json +1 -0
- package/examples/dog-gallery/.astro/dev-local.json +8 -0
- package/examples/dog-gallery/.astro/dev.json +13 -0
- package/examples/dog-gallery/.astro/dev.log +49 -0
- package/examples/dog-gallery/.astro/settings.json +5 -0
- package/examples/dog-gallery/.astro/types.d.ts +2 -0
- package/examples/dog-gallery/dist/_astro/GalleryGrid.astro_astro_type_script_index_0_lang.B0kxDyGz.js +1 -0
- package/examples/dog-gallery/dist/_astro/SitePage.BFHZm5UG.css +1 -0
- package/examples/dog-gallery/dist/dog-care/index.html +15 -0
- package/examples/dog-gallery/dist/favicon.svg +7 -0
- package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-1080.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-1440.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-1536.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-480.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-768.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/black-dogs/photo-of-a-black-dog-5cf614d2-480.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/black-dogs/photo-of-a-black-dog-5cf614d2-768.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/black-dogs/photo-of-a-black-dog-5cf614d2-960.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-1080.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-1440.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-1920.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-3939.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-480.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-768.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-1080.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-1440.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-1920.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-2400.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-480.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-768.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-1080.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-1440.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-1600.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-480.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-768.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-1080.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-1440.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-1920.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-2576.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-480.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-768.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-1080.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-1277.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-480.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-768.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-1080.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-1440.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-1536.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-480.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-768.webp +0 -0
- package/examples/dog-gallery/dist/index.html +43 -0
- package/examples/dog-gallery/dist/robots.txt +2 -0
- package/examples/dog-gallery/node_modules/.vite/deps/_metadata.json +74 -0
- package/examples/dog-gallery/node_modules/.vite/deps/astro-D_GIpJIE.js +434 -0
- package/examples/dog-gallery/node_modules/.vite/deps/astro-D_GIpJIE.js.map +1 -0
- package/examples/dog-gallery/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
- package/examples/dog-gallery/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
- package/examples/dog-gallery/node_modules/.vite/deps/astro_n_axobject-query.js +2702 -0
- package/examples/dog-gallery/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
- package/examples/dog-gallery/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
- package/examples/dog-gallery/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
- package/examples/dog-gallery/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
- package/examples/dog-gallery/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
- package/examples/dog-gallery/node_modules/.vite/deps/audit-DM9Hkl5B.js +1534 -0
- package/examples/dog-gallery/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +1 -0
- package/examples/dog-gallery/node_modules/.vite/deps/highlight-DnZ1ULOL.js +48 -0
- package/examples/dog-gallery/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +1 -0
- package/examples/dog-gallery/node_modules/.vite/deps/icons-RyebKi45.js +42 -0
- package/examples/dog-gallery/node_modules/.vite/deps/icons-RyebKi45.js.map +1 -0
- package/examples/dog-gallery/node_modules/.vite/deps/package.json +3 -0
- package/examples/dog-gallery/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +4 -0
- package/examples/dog-gallery/node_modules/.vite/deps/settings-DaU-gNnK.js +190 -0
- package/examples/dog-gallery/node_modules/.vite/deps/settings-DaU-gNnK.js.map +1 -0
- package/examples/dog-gallery/node_modules/.vite/deps/toolbar-63PA619H.js +458 -0
- package/examples/dog-gallery/node_modules/.vite/deps/toolbar-63PA619H.js.map +1 -0
- package/examples/dog-gallery/node_modules/.vite/deps/ui-library--bkcNSYz.js +1016 -0
- package/examples/dog-gallery/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +1 -0
- package/examples/dog-gallery/node_modules/.vite/deps/window-jPkpKVcy.js +32 -0
- package/examples/dog-gallery/node_modules/.vite/deps/window-jPkpKVcy.js.map +1 -0
- package/examples/dog-gallery/node_modules/.vite/deps/xray-BXCHpEJP.js +111 -0
- package/examples/dog-gallery/node_modules/.vite/deps/xray-BXCHpEJP.js.map +1 -0
- package/examples/dog-gallery/site/.norna/generated-images.json +226 -0
- package/examples/dog-gallery/site/config.mjs +67 -0
- package/examples/dog-gallery/site/content.md +168 -0
- package/examples/dog-gallery/site/images/black-dogs/black-puppy-meadow.png +0 -0
- package/examples/dog-gallery/site/images/black-dogs/photo-of-a-black-dog.jpg +0 -0
- package/examples/dog-gallery/site/images/brown-dogs/brown-dog.jpg +0 -0
- package/examples/dog-gallery/site/images/brown-dogs/dog-accompanies-master.jpg +0 -0
- package/examples/dog-gallery/site/images/golden-dogs/golden-retriever.jpg +0 -0
- package/examples/dog-gallery/site/images/golden-dogs/toller-puppy.jpg +0 -0
- package/examples/dog-gallery/site/images/white-dogs/white-cute-dog.jpg +0 -0
- package/examples/dog-gallery/site/images/white-dogs/white-puppy-garden.png +0 -0
- package/examples/dog-gallery/site/public/favicon.svg +7 -0
- package/examples/dog-gallery/site/public/robots.txt +2 -0
- package/examples/dog-gallery/site/routes/dog-care/route-content.md +49 -0
- package/examples/dog-gallery/site/theme.md +114 -0
- package/fixtures/basic/site/config.mjs +3 -22
- package/fixtures/basic/site/content.md +1 -1
- package/fixtures/basic/site/theme.md +7 -3
- package/package.json +10 -9
- package/scripts/check-config.mjs +4 -3
- package/scripts/deploy-site.mjs +3 -3
- package/scripts/dev-local.mjs +64 -15
- package/scripts/init-site.mjs +11 -9
- package/scripts/lib/frontmatter-yaml.mjs +85 -0
- package/scripts/lib/presentation.mjs +10 -11
- package/scripts/lib/project-config.mjs +113 -45
- package/scripts/lib/site-content.mjs +21 -2
- package/scripts/lib/site-paths.mjs +18 -3
- package/scripts/lib/theme-config.mjs +34 -0
- package/scripts/lib/typography.mjs +103 -23
- package/scripts/show-typography.mjs +202 -75
- package/scripts/sync-content-sections.mjs +6 -6
- package/scripts/test-cli-discovery.mjs +124 -0
- package/scripts/test-content-check.mjs +3 -4
- package/scripts/test-engine-commands.mjs +20 -6
- package/scripts/test-navigation.mjs +10 -6
- package/scripts/test-package-check.mjs +40 -11
- package/scripts/test-temporary-visibility.mjs +2 -3
- package/src/components/SiteNavigation.astro +7 -5
- package/src/components/SitePage.astro +5 -3
- package/src/components/SiteSection.astro +63 -29
- package/src/content.config.ts +64 -2
- package/src/layouts/BaseLayout.astro +19 -1
- package/src/lib/basePath.ts +21 -0
- package/src/lib/generatedImages.ts +8 -4
- package/src/lib/sectionContent.ts +6 -1
- package/src/lib/sitePublicAssets.ts +8 -1
- package/src/styles/global.css +59 -55
- package/starters/basic/.github/workflows/deploy.yml +3 -3
- package/starters/basic/README.md +30 -14
- package/starters/basic/package.json +3 -3
- package/starters/basic/site/config.mjs +4 -24
- package/starters/basic/site/content.md +44 -9
- package/starters/basic/site/theme.md +113 -16
- package/starters/project/.github/workflows/deploy.yml +65 -0
- package/starters/project/README.md +72 -0
- package/starters/project/package-lock.json +4296 -0
- package/starters/project/package.json +35 -0
- package/starters/project/site/config.mjs +43 -0
- package/starters/project/site/content.md +88 -0
- package/starters/project/site/public/robots.txt +2 -0
- package/starters/project/site/routes/guide/route-content.md +58 -0
- package/starters/project/site/theme.md +15 -0
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# norna
|
|
2
2
|
|
|
3
|
-
`norna` is a reusable command-line toolchain for small static
|
|
4
|
-
|
|
3
|
+
`norna` is a reusable command-line toolchain for small static websites.
|
|
4
|
+
It provides the CLI, Astro renderer, validation scripts, image pipeline,
|
|
5
5
|
starter project, fixtures, and deploy helpers used by site repositories such as
|
|
6
6
|
`www.walde.se`.
|
|
7
7
|
|
|
@@ -21,10 +21,11 @@ belong in each site repository. For example, Karin Walde's site lives in
|
|
|
21
21
|
A `norna` site is file-driven:
|
|
22
22
|
|
|
23
23
|
1. A site repository depends on this package.
|
|
24
|
-
2. The site keeps technical settings, including
|
|
25
|
-
|
|
26
|
-
3. The site
|
|
27
|
-
|
|
24
|
+
2. The site keeps technical settings, including public URL and optional URL
|
|
25
|
+
base path, in `site/config.mjs`.
|
|
26
|
+
3. The site keeps site-wide visual theme defaults, including layout, image
|
|
27
|
+
sizing, font, typography, colors, and frame colors, in `site/theme.md`.
|
|
28
|
+
4. The site keeps homepage content, section order, image rows, alt text, and
|
|
28
29
|
captions in `site/content.md`.
|
|
29
30
|
5. Optional route pages live under
|
|
30
31
|
`site/routes/<route-folder>/route-content.md`.
|
|
@@ -35,7 +36,10 @@ A `norna` site is file-driven:
|
|
|
35
36
|
Astro pages, and can help publish the committed branch through GitHub Pages.
|
|
36
37
|
|
|
37
38
|
The default site directory is `site/`. Commands can also use another directory
|
|
38
|
-
with `NORNA_SITE_DIR` or `norna --site-dir <path>`.
|
|
39
|
+
with `NORNA_SITE_DIR` or `norna --site-dir <path>`. Without an explicit site
|
|
40
|
+
directory, commands first accept the current directory when it contains
|
|
41
|
+
`config.mjs` and `content.md`; otherwise they walk upward looking for a
|
|
42
|
+
`site/` directory with those files.
|
|
39
43
|
|
|
40
44
|
## Quick Start
|
|
41
45
|
|
|
@@ -52,10 +56,10 @@ For a new site repository:
|
|
|
52
56
|
|
|
53
57
|
```sh
|
|
54
58
|
cd ../
|
|
55
|
-
npx @janga/norna@latest init my-
|
|
56
|
-
cd my-
|
|
59
|
+
npx @janga/norna@latest init my-site
|
|
60
|
+
cd my-site
|
|
57
61
|
npm install
|
|
58
|
-
npm run
|
|
62
|
+
npm run dev
|
|
59
63
|
```
|
|
60
64
|
|
|
61
65
|
Run `init` before `npm install`. A new site directory is not a Node project
|
|
@@ -71,16 +75,19 @@ Actions use the same engine version.
|
|
|
71
75
|
|
|
72
76
|
- Create a site: [Getting Started](docs/getting-started.md)
|
|
73
77
|
- Understand required site files: [Site Structure](docs/site-structure.md)
|
|
74
|
-
- Edit sections and
|
|
78
|
+
- Edit sections and image rows: [Content](docs/content.md)
|
|
75
79
|
- Configure a site: [Configuration](docs/configuration.md)
|
|
76
|
-
-
|
|
77
|
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
+
- Publish under a GitHub Pages project path:
|
|
81
|
+
[`site.basePath`](docs/configuration.md#sitebasepath)
|
|
82
|
+
- Configure site-wide theme: [Theme](docs/theme.md)
|
|
83
|
+
- Add route pages: [Routes](docs/routes.md)
|
|
84
|
+
- Set page width: [`layout.pageWidth`](docs/theme.md#layout)
|
|
85
|
+
- Set side gutters: [`layout.gutter`](docs/theme.md#layout)
|
|
86
|
+
- Set image area width: [`gallery.width`](docs/theme.md#image-sizing)
|
|
80
87
|
- Keep images within viewport height:
|
|
81
|
-
[`gallery.maxAvailableHeightPercent`](docs/
|
|
82
|
-
- Set the site font: [`typography.fontFamily`](docs/
|
|
83
|
-
- Choose typography presets and overrides: [
|
|
88
|
+
[`gallery.maxAvailableHeightPercent`](docs/theme.md#image-sizing)
|
|
89
|
+
- Set the site font: [`typography.fontFamily`](docs/theme.md#typography)
|
|
90
|
+
- Choose typography presets and overrides: [Typography](docs/typography.md)
|
|
84
91
|
- Look up CLI and npm scripts: [Commands](docs/commands.md)
|
|
85
92
|
- Understand generated images: [Images And Metadata](docs/images-and-metadata.md)
|
|
86
93
|
- Run local preview: [Local Development](docs/local-development.md)
|
|
@@ -98,8 +105,15 @@ Actions use the same engine version.
|
|
|
98
105
|
GitHub Pages workflows created from the starter install the image tools during
|
|
99
106
|
deployment.
|
|
100
107
|
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
`norna` is licensed under [GNU GPL v3](LICENSE).
|
|
111
|
+
|
|
101
112
|
## Documentation
|
|
102
113
|
|
|
114
|
+
The Norna-built introduction site lives in [`site/`](site/) and is configured
|
|
115
|
+
for GitHub Pages at <https://janga.github.io/norna/>.
|
|
116
|
+
|
|
103
117
|
Start with [docs/README.md](docs/README.md) for the documentation map and
|
|
104
118
|
recommended reading order.
|
|
105
119
|
|
package/astro.config.mjs
CHANGED
|
@@ -7,9 +7,11 @@ import {
|
|
|
7
7
|
astroPublicDir,
|
|
8
8
|
engineRoot,
|
|
9
9
|
} from './scripts/lib/site-paths.mjs';
|
|
10
|
+
import projectConfig from './scripts/lib/project-config.mjs';
|
|
10
11
|
|
|
11
12
|
// https://astro.build/config
|
|
12
13
|
export default defineConfig({
|
|
14
|
+
base: projectConfig.site.basePath,
|
|
13
15
|
cacheDir: astroCacheDir,
|
|
14
16
|
outDir: astroDistDir,
|
|
15
17
|
publicDir: astroPublicDir,
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
process.env.NORNA_INVOCATION_ROOT ??= process.cwd();
|
|
6
|
+
|
|
7
|
+
const usage = `
|
|
8
|
+
Usage: norna <command> [options]
|
|
9
|
+
|
|
10
|
+
Commands:
|
|
11
|
+
dev:local Start local Astro dev server
|
|
12
|
+
dev:lan Start dev server for this local network
|
|
13
|
+
dev:restart Restart local Astro dev server
|
|
14
|
+
dev:status Show local dev server status
|
|
15
|
+
dev:logs Show local dev server logs
|
|
16
|
+
dev:stop Stop local dev server
|
|
17
|
+
config:check Validate site/config.mjs
|
|
18
|
+
content:check Validate site/content.md and image references
|
|
19
|
+
content:sync Rewrite section order and move misplaced images
|
|
20
|
+
typography presets Show built-in typography preset values
|
|
21
|
+
typography show Show resolved typography for the selected site
|
|
22
|
+
site:public Sync site/public/ to public/
|
|
23
|
+
images Generate optimized image variants
|
|
24
|
+
engine:update Update @janga/norna in a site repository
|
|
25
|
+
engine:version Show installed engine and Astro versions
|
|
26
|
+
init Create a new site project from the starter
|
|
27
|
+
build Build the selected site
|
|
28
|
+
build:local Build and restart local dev server
|
|
29
|
+
deploy Build and deploy committed branch
|
|
30
|
+
deploy:commit Build, commit allowed changes, push, and check Pages
|
|
31
|
+
deploy:watch Watch GitHub Pages workflow
|
|
32
|
+
preview Preview dist/
|
|
33
|
+
astro Run Astro with norna config
|
|
34
|
+
doctor Print resolved paths
|
|
35
|
+
|
|
36
|
+
Global options:
|
|
37
|
+
--site-dir <path> Use a specific site source directory
|
|
38
|
+
-h, --help Show this help
|
|
39
|
+
`.trim();
|
|
40
|
+
|
|
41
|
+
const parseArgs = (rawArgs) => {
|
|
42
|
+
const commandArgs = [];
|
|
43
|
+
let siteDir = null;
|
|
44
|
+
|
|
45
|
+
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
46
|
+
const arg = rawArgs[index];
|
|
47
|
+
|
|
48
|
+
if (arg === '--site-dir') {
|
|
49
|
+
const value = rawArgs[index + 1];
|
|
50
|
+
if (!value || value.startsWith('-')) {
|
|
51
|
+
throw new Error('--site-dir requires a path.');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
siteDir = value;
|
|
55
|
+
index += 1;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (arg.startsWith('--site-dir=')) {
|
|
60
|
+
siteDir = arg.slice('--site-dir='.length);
|
|
61
|
+
if (!siteDir) {
|
|
62
|
+
throw new Error('--site-dir requires a path.');
|
|
63
|
+
}
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
commandArgs.push(arg);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { commandArgs, siteDir };
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
let parsedArgs;
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
parsedArgs = parseArgs(args);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const { commandArgs, siteDir } = parsedArgs;
|
|
83
|
+
|
|
84
|
+
if (siteDir) {
|
|
85
|
+
process.env.NORNA_SITE_DIR = siteDir;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const [command = 'help', ...rest] = commandArgs;
|
|
89
|
+
const [subcommand, ...subcommandRest] = rest;
|
|
90
|
+
|
|
91
|
+
if (command === '-h' || command === '--help' || command === 'help') {
|
|
92
|
+
console.log(usage);
|
|
93
|
+
process.exit(0);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const [
|
|
97
|
+
{ runAstroInherit },
|
|
98
|
+
{ engineRoot, siteProjectRoot },
|
|
99
|
+
{ runInherit },
|
|
100
|
+
] = await Promise.all([
|
|
101
|
+
import('../scripts/lib/astro-command.mjs'),
|
|
102
|
+
import('../scripts/lib/site-paths.mjs'),
|
|
103
|
+
import('../scripts/lib/run-command.mjs'),
|
|
104
|
+
]);
|
|
105
|
+
|
|
106
|
+
const runScript = (relativePath, scriptArgs = []) => runInherit(
|
|
107
|
+
process.execPath,
|
|
108
|
+
[path.join(engineRoot, relativePath), ...scriptArgs],
|
|
109
|
+
{ cwd: siteProjectRoot },
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const runBuild = () => runScript('scripts/build-site.mjs');
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
if (command === 'dev' || command === 'dev:local') {
|
|
116
|
+
await runScript('scripts/dev-local.mjs', rest);
|
|
117
|
+
} else if (command === 'dev:lan') {
|
|
118
|
+
await runScript('scripts/dev-local.mjs', ['lan', ...rest]);
|
|
119
|
+
} else if (command === 'dev:restart') {
|
|
120
|
+
await runScript('scripts/dev-local.mjs', ['restart', ...rest]);
|
|
121
|
+
} else if (command === 'dev:status') {
|
|
122
|
+
await runScript('scripts/dev-local.mjs', ['status', ...rest]);
|
|
123
|
+
} else if (command === 'dev:logs') {
|
|
124
|
+
await runScript('scripts/dev-local.mjs', ['logs', ...rest]);
|
|
125
|
+
} else if (command === 'dev:stop') {
|
|
126
|
+
await runScript('scripts/dev-local.mjs', ['stop', ...rest]);
|
|
127
|
+
} else if (command === 'config:check') {
|
|
128
|
+
await runScript('scripts/check-config.mjs', rest);
|
|
129
|
+
} else if (command === 'content:check') {
|
|
130
|
+
await runScript('scripts/sync-content-sections.mjs', ['--check', ...rest]);
|
|
131
|
+
await runAstroInherit(['sync']);
|
|
132
|
+
} else if (command === 'content:sync') {
|
|
133
|
+
await runScript('scripts/sync-content-sections.mjs', ['--write', ...rest]);
|
|
134
|
+
} else if (command === 'typography:presets' || (command === 'typography' && subcommand === 'presets')) {
|
|
135
|
+
const scriptArgs = command === 'typography' ? subcommandRest : rest;
|
|
136
|
+
await runScript('scripts/show-typography.mjs', ['presets', ...scriptArgs]);
|
|
137
|
+
} else if (command === 'typography:show' || (command === 'typography' && subcommand === 'show')) {
|
|
138
|
+
const scriptArgs = command === 'typography' ? subcommandRest : rest;
|
|
139
|
+
await runScript('scripts/show-typography.mjs', ['show', ...scriptArgs]);
|
|
140
|
+
} else if (command === 'typography') {
|
|
141
|
+
throw new Error(`Unknown typography command: ${subcommand ?? ''}\n${usage}`);
|
|
142
|
+
} else if (command === 'site:public') {
|
|
143
|
+
await runScript('scripts/sync-site-public.mjs', rest);
|
|
144
|
+
} else if (command === 'images') {
|
|
145
|
+
await runScript('scripts/generate-images.mjs', rest);
|
|
146
|
+
} else if (command === 'engine:update') {
|
|
147
|
+
await runScript('scripts/update-engine.mjs', rest);
|
|
148
|
+
} else if (command === 'engine:version') {
|
|
149
|
+
await runScript('scripts/engine-version.mjs', rest);
|
|
150
|
+
} else if (command === 'init') {
|
|
151
|
+
await runScript('scripts/init-site.mjs', rest);
|
|
152
|
+
} else if (command === 'build') {
|
|
153
|
+
await runBuild();
|
|
154
|
+
} else if (command === 'build:local') {
|
|
155
|
+
await runBuild();
|
|
156
|
+
await runScript('scripts/dev-local.mjs', ['restart']);
|
|
157
|
+
} else if (command === 'deploy') {
|
|
158
|
+
await runScript('scripts/deploy-site.mjs', rest);
|
|
159
|
+
} else if (command === 'deploy:commit') {
|
|
160
|
+
await runScript('scripts/deploy-site.mjs', ['commit', ...rest]);
|
|
161
|
+
} else if (command === 'deploy:watch') {
|
|
162
|
+
await runScript('scripts/watch-pages-deploy.mjs', rest);
|
|
163
|
+
} else if (command === 'doctor') {
|
|
164
|
+
await runScript('scripts/doctor.mjs', rest);
|
|
165
|
+
} else if (command === 'astro') {
|
|
166
|
+
await runAstroInherit(rest);
|
|
167
|
+
} else if (command === 'preview') {
|
|
168
|
+
await runAstroInherit(['preview', ...rest]);
|
|
169
|
+
} else {
|
|
170
|
+
throw new Error(`Unknown command: ${command}\n${usage}`);
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
package/bin/norna.mjs
CHANGED
|
@@ -1,170 +1,169 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { readFile, realpath } from 'node:fs/promises';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
2
6
|
import path from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
3
8
|
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
dev:stop Stop local dev server
|
|
17
|
-
config:check Validate site/config.mjs
|
|
18
|
-
content:check Validate site/content.md and gallery references
|
|
19
|
-
content:sync Rewrite section order and move misplaced images
|
|
20
|
-
typography:presets Show built-in typography preset values
|
|
21
|
-
typography:show Show resolved typography for the selected site
|
|
22
|
-
site:public Sync site/public/ to public/
|
|
23
|
-
images Generate optimized image variants
|
|
24
|
-
engine:update Update @janga/norna in a site repository
|
|
25
|
-
engine:version Show installed engine and Astro versions
|
|
26
|
-
init Create a new site project from the starter
|
|
27
|
-
build Build the selected site
|
|
28
|
-
build:local Build and restart local dev server
|
|
29
|
-
deploy Build and deploy committed branch
|
|
30
|
-
deploy:commit Build, commit allowed changes, push, and check Pages
|
|
31
|
-
deploy:watch Watch GitHub Pages workflow
|
|
32
|
-
preview Preview dist/
|
|
33
|
-
astro Run Astro with norna config
|
|
34
|
-
doctor Print resolved paths
|
|
35
|
-
|
|
36
|
-
Global options:
|
|
37
|
-
--site-dir <path> Use a specific site source directory
|
|
38
|
-
-h, --help Show this help
|
|
39
|
-
`.trim();
|
|
40
|
-
|
|
41
|
-
const parseArgs = (rawArgs) => {
|
|
42
|
-
const commandArgs = [];
|
|
43
|
-
let siteDir = null;
|
|
44
|
-
|
|
45
|
-
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
46
|
-
const arg = rawArgs[index];
|
|
47
|
-
|
|
48
|
-
if (arg === '--site-dir') {
|
|
49
|
-
const value = rawArgs[index + 1];
|
|
50
|
-
if (!value || value.startsWith('-')) {
|
|
51
|
-
throw new Error('--site-dir requires a path.');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
siteDir = value;
|
|
55
|
-
index += 1;
|
|
56
|
-
continue;
|
|
9
|
+
const packageName = '@janga/norna';
|
|
10
|
+
const currentEntrypoint = fileURLToPath(import.meta.url);
|
|
11
|
+
const implementationEntrypoint = new URL('./norna-cli.mjs', import.meta.url);
|
|
12
|
+
|
|
13
|
+
const readJson = async (filePath) => JSON.parse(await readFile(filePath, 'utf8'));
|
|
14
|
+
|
|
15
|
+
const readJsonOptional = async (filePath) => {
|
|
16
|
+
try {
|
|
17
|
+
return await readJson(filePath);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
if (error.code === 'ENOENT') {
|
|
20
|
+
return null;
|
|
57
21
|
}
|
|
58
22
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const realpathOptional = async (filePath) => {
|
|
28
|
+
try {
|
|
29
|
+
return await realpath(filePath);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (error.code === 'ENOENT') {
|
|
32
|
+
return null;
|
|
65
33
|
}
|
|
66
34
|
|
|
67
|
-
|
|
35
|
+
throw error;
|
|
68
36
|
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const findNearestProjectRoot = (startDirectory) => {
|
|
40
|
+
let current = path.resolve(startDirectory);
|
|
41
|
+
|
|
42
|
+
while (true) {
|
|
43
|
+
if (existsSync(path.join(current, 'package.json'))) {
|
|
44
|
+
return current;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const parent = path.dirname(current);
|
|
48
|
+
if (parent === current) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
69
51
|
|
|
70
|
-
|
|
52
|
+
current = parent;
|
|
53
|
+
}
|
|
71
54
|
};
|
|
72
55
|
|
|
73
|
-
|
|
56
|
+
const isWithinDirectory = (parentDirectory, childDirectory) => {
|
|
57
|
+
const relativePath = path.relative(parentDirectory, childDirectory);
|
|
58
|
+
return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));
|
|
59
|
+
};
|
|
74
60
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
61
|
+
const declaresNornaDependency = (packageJson) => [
|
|
62
|
+
'dependencies',
|
|
63
|
+
'devDependencies',
|
|
64
|
+
'optionalDependencies',
|
|
65
|
+
'peerDependencies',
|
|
66
|
+
].some((field) => Object.hasOwn(packageJson[field] ?? {}, packageName));
|
|
81
67
|
|
|
82
|
-
const
|
|
68
|
+
const getPackageBinEntrypoint = (packageRoot, packageJson) => {
|
|
69
|
+
const bin = packageJson.bin;
|
|
70
|
+
const binPath = typeof bin === 'string' ? bin : bin?.norna;
|
|
83
71
|
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
}
|
|
72
|
+
if (!binPath) {
|
|
73
|
+
throw new Error(`${packageName} does not define a norna bin entrypoint.`);
|
|
74
|
+
}
|
|
87
75
|
|
|
88
|
-
|
|
76
|
+
return path.resolve(packageRoot, binPath);
|
|
77
|
+
};
|
|
89
78
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
79
|
+
const resolveLocalEntrypoint = async () => {
|
|
80
|
+
const currentPackageRoot = findNearestProjectRoot(path.dirname(currentEntrypoint));
|
|
81
|
+
const currentPackageJson = currentPackageRoot
|
|
82
|
+
? await readJsonOptional(path.join(currentPackageRoot, 'package.json'))
|
|
83
|
+
: null;
|
|
84
|
+
|
|
85
|
+
if (
|
|
86
|
+
currentPackageJson?.name === packageName
|
|
87
|
+
&& isWithinDirectory(currentPackageRoot, process.cwd())
|
|
88
|
+
) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const projectRoot = findNearestProjectRoot(process.cwd());
|
|
93
|
+
if (!projectRoot) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const projectPackageJson = await readJsonOptional(path.join(projectRoot, 'package.json'));
|
|
98
|
+
if (!projectPackageJson || projectPackageJson.name === packageName || !declaresNornaDependency(projectPackageJson)) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let localPackageJsonPath;
|
|
103
|
+
try {
|
|
104
|
+
const requireFromProject = createRequire(path.join(projectRoot, 'package.json'));
|
|
105
|
+
localPackageJsonPath = requireFromProject.resolve(`${packageName}/package.json`);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (error.code === 'MODULE_NOT_FOUND') {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const localPackageJson = await readJson(localPackageJsonPath);
|
|
115
|
+
const localPackageRoot = path.dirname(localPackageJsonPath);
|
|
116
|
+
return getPackageBinEntrypoint(localPackageRoot, localPackageJson);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const runLocalEntrypoint = (entrypoint) => {
|
|
120
|
+
const child = spawn(process.execPath, [entrypoint, ...process.argv.slice(2)], {
|
|
121
|
+
cwd: process.cwd(),
|
|
122
|
+
env: process.env,
|
|
123
|
+
stdio: 'inherit',
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const forwardSignal = (signal) => {
|
|
127
|
+
if (!child.killed) {
|
|
128
|
+
child.kill(signal);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const signals = ['SIGINT', 'SIGTERM'];
|
|
132
|
+
for (const signal of signals) {
|
|
133
|
+
process.once(signal, forwardSignal);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
child.once('exit', (code, signal) => {
|
|
137
|
+
for (const forwardedSignal of signals) {
|
|
138
|
+
process.removeListener(forwardedSignal, forwardSignal);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (signal) {
|
|
142
|
+
process.kill(process.pid, signal);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
process.exit(code ?? 1);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
child.once('error', (error) => {
|
|
150
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
151
|
+
process.exit(1);
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const localEntrypoint = await resolveLocalEntrypoint();
|
|
156
|
+
if (localEntrypoint) {
|
|
157
|
+
const [currentRealpath, localRealpath] = await Promise.all([
|
|
158
|
+
realpathOptional(currentEntrypoint),
|
|
159
|
+
realpathOptional(localEntrypoint),
|
|
160
|
+
]);
|
|
94
161
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
{ engineRoot, siteProjectRoot },
|
|
98
|
-
{ runInherit },
|
|
99
|
-
] = await Promise.all([
|
|
100
|
-
import('../scripts/lib/astro-command.mjs'),
|
|
101
|
-
import('../scripts/lib/site-paths.mjs'),
|
|
102
|
-
import('../scripts/lib/run-command.mjs'),
|
|
103
|
-
]);
|
|
104
|
-
|
|
105
|
-
const runScript = (relativePath, scriptArgs = []) => runInherit(
|
|
106
|
-
process.execPath,
|
|
107
|
-
[path.join(engineRoot, relativePath), ...scriptArgs],
|
|
108
|
-
{ cwd: siteProjectRoot },
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
const runBuild = () => runScript('scripts/build-site.mjs');
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
if (command === 'dev' || command === 'dev:local') {
|
|
115
|
-
await runScript('scripts/dev-local.mjs', rest);
|
|
116
|
-
} else if (command === 'dev:lan') {
|
|
117
|
-
await runScript('scripts/dev-local.mjs', ['lan', ...rest]);
|
|
118
|
-
} else if (command === 'dev:restart') {
|
|
119
|
-
await runScript('scripts/dev-local.mjs', ['restart', ...rest]);
|
|
120
|
-
} else if (command === 'dev:status') {
|
|
121
|
-
await runScript('scripts/dev-local.mjs', ['status', ...rest]);
|
|
122
|
-
} else if (command === 'dev:logs') {
|
|
123
|
-
await runScript('scripts/dev-local.mjs', ['logs', ...rest]);
|
|
124
|
-
} else if (command === 'dev:stop') {
|
|
125
|
-
await runScript('scripts/dev-local.mjs', ['stop', ...rest]);
|
|
126
|
-
} else if (command === 'config:check') {
|
|
127
|
-
await runScript('scripts/check-config.mjs', rest);
|
|
128
|
-
} else if (command === 'content:check') {
|
|
129
|
-
await runScript('scripts/sync-content-sections.mjs', ['--check', ...rest]);
|
|
130
|
-
await runAstroInherit(['sync']);
|
|
131
|
-
} else if (command === 'content:sync') {
|
|
132
|
-
await runScript('scripts/sync-content-sections.mjs', ['--write', ...rest]);
|
|
133
|
-
} else if (command === 'typography:presets') {
|
|
134
|
-
await runScript('scripts/show-typography.mjs', ['presets', ...rest]);
|
|
135
|
-
} else if (command === 'typography:show') {
|
|
136
|
-
await runScript('scripts/show-typography.mjs', ['show', ...rest]);
|
|
137
|
-
} else if (command === 'site:public') {
|
|
138
|
-
await runScript('scripts/sync-site-public.mjs', rest);
|
|
139
|
-
} else if (command === 'images') {
|
|
140
|
-
await runScript('scripts/generate-images.mjs', rest);
|
|
141
|
-
} else if (command === 'engine:update') {
|
|
142
|
-
await runScript('scripts/update-engine.mjs', rest);
|
|
143
|
-
} else if (command === 'engine:version') {
|
|
144
|
-
await runScript('scripts/engine-version.mjs', rest);
|
|
145
|
-
} else if (command === 'init') {
|
|
146
|
-
await runScript('scripts/init-site.mjs', rest);
|
|
147
|
-
} else if (command === 'build') {
|
|
148
|
-
await runBuild();
|
|
149
|
-
} else if (command === 'build:local') {
|
|
150
|
-
await runBuild();
|
|
151
|
-
await runScript('scripts/dev-local.mjs', ['restart']);
|
|
152
|
-
} else if (command === 'deploy') {
|
|
153
|
-
await runScript('scripts/deploy-site.mjs', rest);
|
|
154
|
-
} else if (command === 'deploy:commit') {
|
|
155
|
-
await runScript('scripts/deploy-site.mjs', ['commit', ...rest]);
|
|
156
|
-
} else if (command === 'deploy:watch') {
|
|
157
|
-
await runScript('scripts/watch-pages-deploy.mjs', rest);
|
|
158
|
-
} else if (command === 'doctor') {
|
|
159
|
-
await runScript('scripts/doctor.mjs', rest);
|
|
160
|
-
} else if (command === 'astro') {
|
|
161
|
-
await runAstroInherit(rest);
|
|
162
|
-
} else if (command === 'preview') {
|
|
163
|
-
await runAstroInherit(['preview', ...rest]);
|
|
162
|
+
if (localRealpath && currentRealpath !== localRealpath) {
|
|
163
|
+
runLocalEntrypoint(localEntrypoint);
|
|
164
164
|
} else {
|
|
165
|
-
|
|
165
|
+
await import(implementationEntrypoint.href);
|
|
166
166
|
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
process.exit(1);
|
|
167
|
+
} else {
|
|
168
|
+
await import(implementationEntrypoint.href);
|
|
170
169
|
}
|