@nera-static/nera 1.5.0 → 1.6.0
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 -3
- package/package.json +1 -1
- package/src/scaffold.js +39 -12
- package/template/AGENTS.md +118 -0
- package/template/CLAUDE.md +1 -0
package/README.md
CHANGED
|
@@ -16,14 +16,15 @@ npm run dev # or: nera dev
|
|
|
16
16
|
|
|
17
17
|
Already in the folder the site should live in? `nera new .` scaffolds into the
|
|
18
18
|
current folder instead. The folder must be empty apart from dotfiles such as
|
|
19
|
-
`.git`
|
|
20
|
-
|
|
19
|
+
`.git` and an `AGENTS.md`/`CLAUDE.md` an AI assistant may have written first
|
|
20
|
+
(an existing `.gitignore`, `AGENTS.md` or `CLAUDE.md` is kept), and the
|
|
21
|
+
package is named after the folder (`My Bakery` → `my-bakery`).
|
|
21
22
|
|
|
22
23
|
## Commands
|
|
23
24
|
|
|
24
25
|
| Command | What it does |
|
|
25
26
|
|---|---|
|
|
26
|
-
| `nera new <name>` | Scaffold a new thin Nera site (one dependency: `@nera-static/nera`). Add `--theme <name>` to start from a theme. `nera new .` scaffolds into the current, empty folder. |
|
|
27
|
+
| `nera new <name>` | Scaffold a new thin Nera site (one dependency: `@nera-static/nera`). Add `--theme <name>` to start from a theme. `nera new .` scaffolds into the current, empty folder (dotfiles, `AGENTS.md` and `CLAUDE.md` allowed). |
|
|
27
28
|
| `nera build` | Render `pages/` → `public/`. With `--check`, then run `nera check` on the result — one command for CI. |
|
|
28
29
|
| `nera dev` | Build, serve `public/`, and rebuild on change with live reload. Watches `pages/`, `config/`, `theme/` and a local theme (`theme: ./themes/<name>`). |
|
|
29
30
|
| `nera serve` | Serve the already-built `public/` folder. |
|
|
@@ -41,6 +42,20 @@ dependency and its scripts call the CLI:
|
|
|
41
42
|
}
|
|
42
43
|
```
|
|
43
44
|
|
|
45
|
+
## Instructions for AI assistants
|
|
46
|
+
|
|
47
|
+
Every scaffolded site carries an `AGENTS.md`, the file Codex, Cursor, Copilot
|
|
48
|
+
and other assistants read first, and a `CLAUDE.md` holding the single line
|
|
49
|
+
`@AGENTS.md`, so Claude Code loads the same text. `AGENTS.md` covers what
|
|
50
|
+
Nera is, the folder map, the commands, the common traps, and the check loop:
|
|
51
|
+
`nera validate`, then `nera build --check`, after every change.
|
|
52
|
+
|
|
53
|
+
It ends with a **Notes for this site** section for your own instructions —
|
|
54
|
+
brand voice, pages not to touch, where the site is deployed. It starts below
|
|
55
|
+
the marker line `<!-- nera:site-notes … -->`; keep that line in place. The
|
|
56
|
+
part above it is Nera's and may be replaced by a later Nera version; your
|
|
57
|
+
notes below it are yours.
|
|
58
|
+
|
|
44
59
|
## Starting from a theme
|
|
45
60
|
|
|
46
61
|
```bash
|
package/package.json
CHANGED
package/src/scaffold.js
CHANGED
|
@@ -34,27 +34,46 @@ const CURRENT_DIR = new Set(['.', './'])
|
|
|
34
34
|
|
|
35
35
|
export const isCurrentDir = (projectName) => CURRENT_DIR.has(projectName)
|
|
36
36
|
|
|
37
|
+
// Letters NFKD does not decompose into a base letter plus an accent.
|
|
38
|
+
const LETTER_FOLDS = { ß: 'ss', æ: 'ae', œ: 'oe', ø: 'o', ł: 'l', đ: 'd' }
|
|
39
|
+
|
|
40
|
+
// npm refuses these names outright, and longer ones than this.
|
|
41
|
+
const NPM_RESERVED = new Set(['node_modules', 'favicon.ico'])
|
|
42
|
+
const NPM_MAX_LENGTH = 214
|
|
43
|
+
|
|
37
44
|
// The package name for a site scaffolded in place, derived from the folder's
|
|
38
|
-
// basename and normalised until it passes validateProjectName
|
|
39
|
-
// dropped (`Bäckerei` → `backerei`),
|
|
40
|
-
// → one `-`, no leading dot, dash or
|
|
45
|
+
// basename and normalised until it passes validateProjectName and npm's own
|
|
46
|
+
// rules: accents dropped (`Bäckerei` → `backerei`, `Straße` → `strasse`),
|
|
47
|
+
// lower-case, each run of other characters → one `-`, no leading dot, dash or
|
|
48
|
+
// underscore, at most 214 characters. `nera-site` when nothing usable is left.
|
|
41
49
|
export function projectNameFromDir(dir) {
|
|
42
50
|
const name = path
|
|
43
51
|
.basename(path.resolve(dir))
|
|
52
|
+
.toLowerCase()
|
|
53
|
+
.replace(/[ßæœøłđ]/g, (c) => LETTER_FOLDS[c])
|
|
44
54
|
.normalize('NFKD')
|
|
45
55
|
.replace(/[\u0300-\u036f]/g, '')
|
|
46
|
-
.toLowerCase()
|
|
47
56
|
.replace(/[^a-z0-9._-]+/g, '-')
|
|
48
57
|
.replace(/^[._-]+/, '')
|
|
49
|
-
.
|
|
50
|
-
|
|
58
|
+
.slice(0, NPM_MAX_LENGTH)
|
|
59
|
+
.replace(/[-.]+$/, '')
|
|
60
|
+
return name === '' || NPM_RESERVED.has(name)
|
|
61
|
+
? 'nera-site'
|
|
62
|
+
: validateProjectName(name)
|
|
51
63
|
}
|
|
52
64
|
|
|
65
|
+
// Files an AI assistant may have written before running `nera new .`. They
|
|
66
|
+
// do not make the folder count as non-empty, and the user's copy is kept.
|
|
67
|
+
// Exact names only, matched case-sensitively.
|
|
68
|
+
const AGENT_FILES = new Set(['AGENTS.md', 'CLAUDE.md'])
|
|
69
|
+
|
|
53
70
|
// In-place scaffolding only goes ahead in an empty folder. Dotfiles such as
|
|
54
71
|
// `.git` or an editor folder do not count: `git init` first is the usual way
|
|
55
|
-
// to start a project.
|
|
72
|
+
// to start a project. Neither do AGENT_FILES.
|
|
56
73
|
async function assertEmptyDir(dir) {
|
|
57
|
-
const entries = (await fs.readdir(dir)).filter(
|
|
74
|
+
const entries = (await fs.readdir(dir)).filter(
|
|
75
|
+
(n) => !n.startsWith('.') && !AGENT_FILES.has(n)
|
|
76
|
+
)
|
|
58
77
|
if (entries.length > 0) {
|
|
59
78
|
throw new Error(
|
|
60
79
|
`The current folder is not empty (${entries.slice(0, 3).join(', ')}` +
|
|
@@ -124,8 +143,9 @@ async function copyDir(src, dest, { skipStarters = false } = {}) {
|
|
|
124
143
|
} else if (skipStarters && (await isStarterTemplate(from))) {
|
|
125
144
|
continue
|
|
126
145
|
} else if (fssync.existsSync(to)) {
|
|
127
|
-
// Only in place, and then only a dotfile such as .gitignore
|
|
128
|
-
// there already (assertEmptyDir):
|
|
146
|
+
// Only in place, and then only a dotfile such as .gitignore, or
|
|
147
|
+
// AGENTS.md / CLAUDE.md, can be there already (assertEmptyDir):
|
|
148
|
+
// the user's copy wins.
|
|
129
149
|
console.log(` • Kept your existing ${path.basename(to)}`)
|
|
130
150
|
} else {
|
|
131
151
|
await fs.copyFile(from, to)
|
|
@@ -195,8 +215,15 @@ export async function scaffoldProject(projectName, options = {}) {
|
|
|
195
215
|
execFileSync('npm', args, { cwd: targetDir, stdio: 'inherit' })
|
|
196
216
|
}
|
|
197
217
|
|
|
218
|
+
// `npm run dev`, not `nera dev`: it works without a global `nera`.
|
|
219
|
+
const nextSteps = [
|
|
220
|
+
...(inPlace ? [] : [`cd ${projectName}`]),
|
|
221
|
+
...(install ? [] : ['npm install']),
|
|
222
|
+
'npm run dev',
|
|
223
|
+
]
|
|
198
224
|
console.log('✅ Done!')
|
|
199
|
-
|
|
200
|
-
|
|
225
|
+
console.log(
|
|
226
|
+
`👉 Next steps:\n${nextSteps.map((l) => ` ${l}\n`).join('')}`
|
|
227
|
+
)
|
|
201
228
|
return targetDir
|
|
202
229
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Instructions for AI coding agents working on this site. People are welcome to
|
|
4
|
+
read them too.
|
|
5
|
+
|
|
6
|
+
## What this is
|
|
7
|
+
|
|
8
|
+
This site is built with [Nera](https://nera.js.org), a static site generator:
|
|
9
|
+
Markdown pages plus Pug templates in, plain HTML out. Nera is not Eleventy,
|
|
10
|
+
Hugo, Astro or Jekyll, so do not apply their conventions here. The docs are
|
|
11
|
+
at https://nera.js.org.
|
|
12
|
+
|
|
13
|
+
## Folder map
|
|
14
|
+
|
|
15
|
+
- `pages/` — the content: one Markdown file with YAML frontmatter per page
|
|
16
|
+
- `theme/views/` — the Pug templates (`layouts/`, `pages/`, and `vendor/`
|
|
17
|
+
for copied plugin templates)
|
|
18
|
+
- `theme/assets/` — CSS, JavaScript, images and fonts, copied into
|
|
19
|
+
`public/` on every build
|
|
20
|
+
- `config/` — YAML settings: `app.yaml` for the site, `<plugin>.yaml` for
|
|
21
|
+
each plugin
|
|
22
|
+
- `src/plugins/<name>/index.js` — local plugins for this site only (optional;
|
|
23
|
+
another folder can be set with `folders.plugins` in `config/app.yaml`)
|
|
24
|
+
- `public/` — the generated site. **Never edit it**: every build deletes it
|
|
25
|
+
and writes it again
|
|
26
|
+
|
|
27
|
+
## Commands
|
|
28
|
+
|
|
29
|
+
- `npm run dev` — build, serve on http://localhost:3000 and rebuild on
|
|
30
|
+
every change
|
|
31
|
+
- `npm run build` — render `pages/` into `public/`
|
|
32
|
+
- `npx nera validate` — check pages, layouts, includes and YAML before a build
|
|
33
|
+
- `npx nera build --check` — build, then check the built `public/` for
|
|
34
|
+
accessibility, privacy and legal issues (`npx nera check` checks without
|
|
35
|
+
building)
|
|
36
|
+
|
|
37
|
+
## After every change
|
|
38
|
+
|
|
39
|
+
1. Run `npx nera validate` and fix every error it reports.
|
|
40
|
+
2. Run `npx nera build --check` and fix what it reports.
|
|
41
|
+
|
|
42
|
+
`nera validate` names the file and line of a broken layout or include and
|
|
43
|
+
finds pages the build would skip silently. Run it first.
|
|
44
|
+
|
|
45
|
+
## How Nera works (and where it trips agents up)
|
|
46
|
+
|
|
47
|
+
- **Pages are Markdown.** Write prose in Markdown, not HTML. Page structure
|
|
48
|
+
and markup shared between pages belong in `theme/views/`.
|
|
49
|
+
- **URLs end in `.html`.** `pages/a/b.md` becomes `public/a/b.html` and is
|
|
50
|
+
linked as `/a/b.html`; `pages/index.md` is `/index.html`. There are no
|
|
51
|
+
pretty URLs such as `/a/b/`.
|
|
52
|
+
- **A page needs `layout`.** The `layout` key in a page's frontmatter names a
|
|
53
|
+
file under `theme/views/`; the pages this site was created with use
|
|
54
|
+
`pages/default.pug`. A page without `layout` is not rendered and the build
|
|
55
|
+
says nothing; `nera validate` warns (`layout-missing`). Renaming or deleting
|
|
56
|
+
a layout breaks every page that uses it.
|
|
57
|
+
- **The page body.** The rendered Markdown reaches the template as `content`.
|
|
58
|
+
Without a theme, `theme/views/pages/default.pug` prints it with
|
|
59
|
+
`main !{ content }` inside `block content`; with a theme, the theme's layout
|
|
60
|
+
may already hold the `<main>`. A page has exactly one `<main>`: check the
|
|
61
|
+
layout and the page template before adding one.
|
|
62
|
+
- **`public/` is wiped on every build.** Put source files in `pages/`,
|
|
63
|
+
`theme/` or `config/`; anything written into `public/` by hand is lost.
|
|
64
|
+
- **Site files override theme files.** If `config/app.yaml` sets `theme:`,
|
|
65
|
+
the layouts come from that theme (a package or a folder), and a file in
|
|
66
|
+
`theme/views/` or `theme/assets/` replaces the theme's file at the same
|
|
67
|
+
path.
|
|
68
|
+
- **Translations.** Templates call `t('key')`, which reads
|
|
69
|
+
`translations.<lang>.key` from `config/app.yaml` (the page's `lang`, else
|
|
70
|
+
the site's).
|
|
71
|
+
|
|
72
|
+
## Plugins: look for one before writing a feature
|
|
73
|
+
|
|
74
|
+
Features come from npm packages named `@nera-static/plugin-*`. Install one with
|
|
75
|
+
`npm install`, and Nera loads it on the next build; there is nothing to
|
|
76
|
+
register. Most read optional settings from `config/<name>.yaml` (the plugin's
|
|
77
|
+
README shows the keys). Plugin catalog:
|
|
78
|
+
https://github.com/seebaermichi/nera/blob/main/PLUGINS.md
|
|
79
|
+
|
|
80
|
+
Check the catalog before hand-writing navigation, a contact form, tags, search,
|
|
81
|
+
pagination or image handling. Two that sites often need:
|
|
82
|
+
|
|
83
|
+
- `@nera-static/plugin-contact-form` — a contact form that opens the visitor's
|
|
84
|
+
mail client (`mailto:`). No backend and no form service; configured in
|
|
85
|
+
`config/contact-form.yaml`.
|
|
86
|
+
- `@nera-static/plugin-navigation` — menus from `config/navigation.yaml`.
|
|
87
|
+
|
|
88
|
+
Plugin templates are not used automatically. Copy them into the site first,
|
|
89
|
+
then include them by hand in your own template:
|
|
90
|
+
|
|
91
|
+
- `npx nera-contact-form` copies the form to
|
|
92
|
+
`theme/views/vendor/plugin-contact-form/` (and its script to
|
|
93
|
+
`theme/assets/js/`); include it with
|
|
94
|
+
`include /vendor/plugin-contact-form/contact-form`
|
|
95
|
+
- `npx nera-navigation` copies the menus to
|
|
96
|
+
`theme/views/vendor/plugin-navigation/`
|
|
97
|
+
|
|
98
|
+
The copy is skipped when the target folder already exists, so later plugin
|
|
99
|
+
updates do not reach it; `--force` overwrites the copy.
|
|
100
|
+
|
|
101
|
+
## Preview
|
|
102
|
+
|
|
103
|
+
`npm run dev` keeps running until it is stopped. If you start it in the
|
|
104
|
+
background, stop that process yourself when you are done, by its process ID.
|
|
105
|
+
Never kill processes by name (`pkill -f nera`), which stops other people's
|
|
106
|
+
servers too. If port 3000 is taken, the server picks another one and prints
|
|
107
|
+
the URL.
|
|
108
|
+
|
|
109
|
+
## Getting online
|
|
110
|
+
|
|
111
|
+
`npm run build` writes the finished site into `public/`. That folder is what
|
|
112
|
+
gets deployed: upload its contents to any static web host.
|
|
113
|
+
|
|
114
|
+
<!-- nera:site-notes — everything below this line belongs to the site owner; `nera update` keeps it as it is -->
|
|
115
|
+
## Notes for this site
|
|
116
|
+
|
|
117
|
+
Add what an agent should know about this particular site: its audience, tone,
|
|
118
|
+
languages, pages that must not change, the hosting.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|