@janga/norna 0.7.1 → 0.7.2
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 +18 -4
- package/astro.config.mjs +2 -0
- package/bin/norna-cli.mjs +170 -0
- package/bin/norna.mjs +149 -150
- package/docs/README.md +36 -16
- package/docs/commands.md +18 -5
- package/docs/configuration.md +37 -2
- package/docs/content.md +98 -257
- package/docs/{command-organization.md → design/command-organization.md} +9 -5
- package/docs/{site-examples-structure-note.md → design/site-examples-structure.md} +17 -22
- package/docs/engine-development.md +33 -5
- package/docs/getting-started.md +40 -4
- package/docs/local-development.md +13 -0
- package/docs/publishing.md +23 -0
- package/docs/routes.md +90 -0
- package/docs/site-structure.md +15 -8
- package/docs/theme.md +150 -0
- package/docs/typography.md +125 -0
- package/examples/dog-gallery/site/.norna/generated-images.json +226 -0
- package/examples/dog-gallery/site/config.mjs +97 -0
- package/examples/dog-gallery/site/content.md +146 -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 +35 -0
- package/examples/dog-gallery/site/theme.md +55 -0
- package/fixtures/basic/site/config.mjs +1 -0
- package/package.json +8 -7
- package/scripts/check-config.mjs +1 -0
- package/scripts/dev-local.mjs +64 -15
- package/scripts/init-site.mjs +1 -1
- package/scripts/lib/project-config.mjs +22 -0
- package/scripts/lib/site-content.mjs +2 -1
- package/scripts/lib/site-paths.mjs +18 -3
- package/scripts/lib/typography.mjs +5 -5
- package/scripts/show-typography.mjs +252 -29
- package/scripts/test-cli-discovery.mjs +124 -0
- package/scripts/test-engine-commands.mjs +18 -4
- package/scripts/test-navigation.mjs +10 -6
- package/scripts/test-package-check.mjs +32 -4
- package/src/components/SiteNavigation.astro +7 -5
- package/src/components/SitePage.astro +3 -0
- package/src/components/SiteSection.astro +24 -24
- package/src/content.config.ts +4 -0
- package/src/layouts/BaseLayout.astro +2 -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 +8 -8
- package/starters/basic/.github/workflows/deploy.yml +3 -3
- package/starters/basic/README.md +21 -0
- package/starters/basic/package.json +1 -1
- package/starters/basic/site/config.mjs +1 -0
- package/starters/basic/site/content.md +5 -3
- package/starters/basic/site/theme.md +4 -0
package/README.md
CHANGED
|
@@ -22,7 +22,8 @@ A `norna` site is file-driven:
|
|
|
22
22
|
|
|
23
23
|
1. A site repository depends on this package.
|
|
24
24
|
2. The site keeps technical settings, including page width, gallery width, and
|
|
25
|
-
the global font family, in
|
|
25
|
+
the global font family, public URL, and optional URL base path in
|
|
26
|
+
`site/config.mjs`.
|
|
26
27
|
3. The site can keep site-wide visual theme defaults in `site/theme.md`.
|
|
27
28
|
4. The site keeps homepage content, section order, gallery rows, alt text, and
|
|
28
29
|
captions in `site/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
|
|
|
@@ -73,14 +77,17 @@ Actions use the same engine version.
|
|
|
73
77
|
- Understand required site files: [Site Structure](docs/site-structure.md)
|
|
74
78
|
- Edit sections and galleries: [Content](docs/content.md)
|
|
75
79
|
- Configure a site: [Configuration](docs/configuration.md)
|
|
76
|
-
-
|
|
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)
|
|
77
84
|
- Set page width: [`layout.pageWidth`](docs/configuration.md#layoutpagewidth)
|
|
78
85
|
- Set side gutters: [`layout.gutter`](docs/configuration.md#layoutgutter)
|
|
79
86
|
- Set gallery width: [`gallery.width`](docs/configuration.md#gallerywidth)
|
|
80
87
|
- Keep images within viewport height:
|
|
81
88
|
[`gallery.maxAvailableHeightPercent`](docs/configuration.md#gallerymaxavailableheightpercent)
|
|
82
89
|
- Set the site font: [`typography.fontFamily`](docs/configuration.md#typographyfontfamily)
|
|
83
|
-
- Choose typography presets and overrides: [
|
|
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,170 @@
|
|
|
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 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;
|
|
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
|
+
|
|
90
|
+
if (command === '-h' || command === '--help' || command === 'help') {
|
|
91
|
+
console.log(usage);
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const [
|
|
96
|
+
{ runAstroInherit },
|
|
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]);
|
|
164
|
+
} else {
|
|
165
|
+
throw new Error(`Unknown command: ${command}\n${usage}`);
|
|
166
|
+
}
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
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
|
}
|
package/docs/README.md
CHANGED
|
@@ -11,18 +11,27 @@ For a site maintainer:
|
|
|
11
11
|
2. [Site Structure](site-structure.md)
|
|
12
12
|
3. [Configuration](configuration.md)
|
|
13
13
|
4. [Content](content.md)
|
|
14
|
-
5. [
|
|
15
|
-
6. [
|
|
16
|
-
7. [
|
|
14
|
+
5. [Theme](theme.md)
|
|
15
|
+
6. [Typography](typography.md)
|
|
16
|
+
7. [Routes](routes.md)
|
|
17
|
+
8. [Images And Metadata](images-and-metadata.md)
|
|
18
|
+
9. [Local Development](local-development.md)
|
|
19
|
+
10. [Publishing](publishing.md)
|
|
17
20
|
|
|
18
21
|
For a developer integrating or updating the engine:
|
|
19
22
|
|
|
20
23
|
1. [Commands](commands.md)
|
|
21
|
-
2. [
|
|
22
|
-
3. [
|
|
23
|
-
4. [
|
|
24
|
-
5. [
|
|
25
|
-
6. [
|
|
24
|
+
2. [Configuration](configuration.md)
|
|
25
|
+
3. [Content](content.md)
|
|
26
|
+
4. [Theme](theme.md)
|
|
27
|
+
5. [Typography](typography.md)
|
|
28
|
+
6. [Routes](routes.md)
|
|
29
|
+
7. [Engine Development](engine-development.md)
|
|
30
|
+
|
|
31
|
+
For design and naming principles:
|
|
32
|
+
|
|
33
|
+
1. [Command Organization](design/command-organization.md)
|
|
34
|
+
2. [Site Examples Structure](design/site-examples-structure.md)
|
|
26
35
|
|
|
27
36
|
For a quick reference:
|
|
28
37
|
|
|
@@ -30,19 +39,30 @@ For a quick reference:
|
|
|
30
39
|
`site/config.mjs` field, including `layout.pageWidth`, `layout.gutter`,
|
|
31
40
|
gallery viewport limits, `typography.fontFamily`, validation rule, and
|
|
32
41
|
default.
|
|
33
|
-
- [Content](content.md) describes page
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
- [Content](content.md) describes page frontmatter, section frontmatter,
|
|
43
|
+
galleries, carousels, temporary sections, Markdown section matching, and
|
|
44
|
+
content validation.
|
|
45
|
+
- [Theme](theme.md) describes site-wide presentation, page and section
|
|
46
|
+
presentation overrides, frame colors, and inline styles.
|
|
47
|
+
- [Typography](typography.md) describes presets, roles, overrides,
|
|
48
|
+
inheritance, and inspection commands.
|
|
49
|
+
- [Routes](routes.md) describes route files, slugs, route navigation, and
|
|
50
|
+
route image directories.
|
|
36
51
|
- [Commands](commands.md) lists the public CLI surface and starter npm scripts.
|
|
37
|
-
- [Command Organization](command-organization.md) defines command namespaces
|
|
38
|
-
for pure gallery projects, mixed projects, and engine development.
|
|
39
52
|
- [Site Structure](site-structure.md) separates versioned source files from
|
|
40
53
|
generated build output.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
54
|
+
|
|
55
|
+
Design documents are intentionally separate from the user reference:
|
|
56
|
+
|
|
57
|
+
- [Command Organization](design/command-organization.md) defines command
|
|
58
|
+
namespaces for pure gallery projects, mixed projects, and engine development.
|
|
59
|
+
- [Site Examples Structure](design/site-examples-structure.md) defines the
|
|
60
|
+
intended vocabulary for starters, examples, documentation sites, reference
|
|
61
|
+
docs, and fixtures.
|
|
44
62
|
|
|
45
63
|
Site repositories should document only their site-specific choices and link here
|
|
46
64
|
for generic `norna` behavior.
|
|
47
65
|
|
|
66
|
+
`norna` is licensed under [GNU GPL v3](../LICENSE).
|
|
67
|
+
|
|
48
68
|
Planning and future work are tracked in [../BACKLOG.md](../BACKLOG.md).
|
package/docs/commands.md
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
# Commands
|
|
2
2
|
|
|
3
|
-
The `norna` binary is the stable command surface.
|
|
4
|
-
scripts
|
|
3
|
+
The `norna` binary is the stable command surface. Site repositories normally
|
|
4
|
+
use starter npm scripts such as `npm run norna:content:check`; those scripts
|
|
5
|
+
call the local `norna` binary from `node_modules/.bin`.
|
|
6
|
+
|
|
7
|
+
Npm installation exposes the command as `norna` on macOS, Linux, and Windows
|
|
8
|
+
through the package `bin` field. When a globally installed `norna` is started
|
|
9
|
+
inside a project that declares and has installed its own `@janga/norna`
|
|
10
|
+
dependency, the launcher delegates to that project-local version. If no
|
|
11
|
+
project-local install is found, the version that was started continues running.
|
|
12
|
+
|
|
13
|
+
Use direct `norna ...` commands only when the binary is on your shell `PATH`,
|
|
14
|
+
inside an npm script, or through `npm exec -- norna ...`.
|
|
5
15
|
|
|
6
16
|
## CLI Commands
|
|
7
17
|
|
|
@@ -97,8 +107,9 @@ should normally mean the repository's complete publishable artifact, while
|
|
|
97
107
|
image files after confirmation.
|
|
98
108
|
- `typography:presets`: prints the exact built-in values for every typography
|
|
99
109
|
preset.
|
|
100
|
-
- `typography:show`: prints the selected site's resolved typography
|
|
101
|
-
|
|
110
|
+
- `typography:show`: prints the selected site's resolved typography for the
|
|
111
|
+
theme, every page route, and every section. Each value includes its source,
|
|
112
|
+
and inherited page or section values are marked with `inherited: true`.
|
|
102
113
|
- `site:public`: copies `site/public/` into `site/.norna/public/` and
|
|
103
114
|
removes stale copied static files.
|
|
104
115
|
- `images`: generates WebP variants and writes
|
|
@@ -120,7 +131,9 @@ should normally mean the repository's complete publishable artifact, while
|
|
|
120
131
|
- `build`: runs config check, content check, public sync, image generation, and
|
|
121
132
|
Astro build.
|
|
122
133
|
- `build:local`: runs `build` and restarts `dev:local`.
|
|
123
|
-
- `dev:local`: starts Astro dev in background mode on `localhost:4321`.
|
|
134
|
+
- `dev:local`: starts Astro dev in background mode on `localhost:4321`. Pass
|
|
135
|
+
`--kill` to stop processes that are blocking the standard port before
|
|
136
|
+
starting.
|
|
124
137
|
- `dev:lan`: starts the same server on all local network interfaces and prints
|
|
125
138
|
the IPv4 URL to open from another device on the same network. Stop it after
|
|
126
139
|
testing because it is accessible to that local network.
|