@nera-static/nera 1.7.1 → 1.9.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 CHANGED
@@ -28,12 +28,12 @@ package is named after the folder (`My Bakery` → `my-bakery`).
28
28
  | Command | What it does |
29
29
  |---|---|
30
30
  | `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). |
31
- | `nera build` | Render `pages/` → `public/`. With `--check`, then run `nera check` on the result — one command for CI. |
31
+ | `nera build` | Render `pages/` → `public/`. With `--check`, then run `nera check` on the result — one command for CI. `--json` prints the result as one JSON object (see [Machine-readable output](#machine-readable-output)). |
32
32
  | `nera dev` | Build, serve `public/`, and rebuild on change with live reload. Watches `pages/`, `config/`, `theme/` and a local theme (`theme: ./themes/<name>`). |
33
33
  | `nera serve` | Serve the already-built `public/` folder. |
34
34
  | `nera update` | Update the site's Nera packages, then create a missing `AGENTS.md`/`CLAUDE.md` and refresh the Nera part of `AGENTS.md` (see [Instructions for AI assistants](#instructions-for-ai-assistants)). On a legacy cloned site, `nera update --migrate` converts it to the thin model. |
35
- | `nera validate` | Check the site's sources (layouts, includes, YAML) before publishing; exits non-zero on any error. |
36
- | `nera check` | Check the built `public/` for accessibility, privacy and legal-notice problems (see below). Build first. |
35
+ | `nera validate` | Check the site's sources (layouts, includes, YAML) before publishing; exits non-zero on any error. `--json` for machine-readable output. |
36
+ | `nera check` | Check the built `public/` for accessibility, privacy and legal-notice problems (see below). Build first. `--json` for machine-readable output. |
37
37
 
38
38
  A scaffolded site is a thin project — no vendored engine, no clone. It lists one
39
39
  dependency and its scripts call the CLI:
@@ -161,6 +161,36 @@ links are found by their text in German and English only (*Impressum*,
161
161
  WCAG criterion or law behind each rule, are in the
162
162
  [`@nera-static/validate` README](https://github.com/seebaermichi/nera-validate#checking-the-built-output).
163
163
 
164
+ ## Machine-readable output
165
+
166
+ `nera validate --json`, `nera check --json` and `nera build --check --json`
167
+ print exactly one JSON object on stdout and nothing else, for an AI agent or a
168
+ script to parse:
169
+
170
+ ```json
171
+ {
172
+ "ok": true,
173
+ "results": [
174
+ {
175
+ "file": "public/index.html",
176
+ "line": 26,
177
+ "severity": "warning",
178
+ "rule": "a11y-skip-link",
179
+ "message": "the first focusable element is not a skip link — …"
180
+ }
181
+ ]
182
+ }
183
+ ```
184
+
185
+ `results` is the validator's list as it is, in the same order (`severity` is
186
+ `error` or `warning`); `ok` is `true` exactly when the exit code is `0`, so
187
+ warnings alone keep it `true`. When the command cannot run — no `public/` for
188
+ `nera check`, a failed build, a config the validator cannot read — stdout holds
189
+ `{ "ok": false, "error": "<message>", "results": [] }` and the exit code is `1`.
190
+ Every log line (the build log, plugin output) goes to stderr, and the exit code
191
+ is the same as without `--json`. `nera build --json` without `--check` prints
192
+ `{ "ok": true, "results": [] }` after a successful build.
193
+
164
194
  ## Migrating a cloned (legacy) site
165
195
 
166
196
  Older Nera sites were git clones that vendored the engine under `src/`. Inside
package/bin/nera.js CHANGED
@@ -8,18 +8,19 @@ import { runValidate } from '../src/commands/validate.js'
8
8
  import { runCheck } from '../src/commands/check.js'
9
9
 
10
10
  const [, , command, ...rest] = process.argv
11
+ const json = rest.includes('--json')
11
12
 
12
13
  const usage = `Nera — the static site generator CLI
13
14
 
14
15
  Usage:
15
16
  nera new <name> scaffold a new Nera site (--theme <name> to start from a theme)
16
17
  nera new . scaffold into the current, empty folder
17
- nera build render pages/ → public/ (--check to check the output after)
18
+ nera build render pages/ → public/ (--check to check the output after, --json for machine-readable output)
18
19
  nera dev build, serve and live-reload
19
20
  nera serve serve the built public/ folder
20
21
  nera update update Nera packages (or migrate a cloned site with --migrate)
21
- nera validate check the site (layouts, includes, YAML) before publish
22
- nera check check the built public/ (accessibility, privacy, legal hints)
22
+ nera validate check the site (layouts, includes, YAML) before publish (--json for machine-readable output)
23
+ nera check check the built public/ (accessibility, privacy, legal hints) (--json for machine-readable output)
23
24
  `
24
25
 
25
26
  try {
@@ -40,10 +41,10 @@ try {
40
41
  await runUpdate(rest)
41
42
  break
42
43
  case 'validate':
43
- process.exit(runValidate())
44
+ process.exitCode = runValidate({ json })
44
45
  break
45
46
  case 'check':
46
- process.exit(runCheck())
47
+ process.exitCode = runCheck({ json })
47
48
  break
48
49
  default:
49
50
  console.log(usage)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nera-static/nera",
3
- "version": "1.7.1",
3
+ "version": "1.9.0",
4
4
  "description": "The Nera CLI — scaffold, build, preview and update a Nera static site with one command over the @nera-static/core engine",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,12 +1,23 @@
1
1
  import run from '@nera-static/core'
2
+ import { validateOutput } from '@nera-static/validate'
2
3
  import { runCheck } from './check.js'
4
+ import { runJson } from '../json-output.js'
3
5
 
4
6
  // Render the current site (pages/ → public/) using the shared engine. `run`
5
7
  // reads config and folders relative to the current working directory, so this
6
8
  // builds whichever site the CLI is invoked in. With `--check`, the fresh output
7
9
  // is then checked as by `nera check` — one command for CI. Returns the exit
8
- // code: the check's, or 0 for a plain build.
10
+ // code: the check's, or 0 for a plain build. With `--json`, the build log goes
11
+ // to stderr and stdout holds one object: the check's `{ ok, results }`, empty
12
+ // results without `--check`, or the failure (see json-output.js).
9
13
  export async function runBuild(args = []) {
14
+ const check = args.includes('--check')
15
+ if (args.includes('--json')) {
16
+ return runJson(async () => {
17
+ await run()
18
+ return check ? validateOutput({ cwd: process.cwd() }) : []
19
+ })
20
+ }
10
21
  await run()
11
- return args.includes('--check') ? runCheck() : 0
22
+ return check ? runCheck() : 0
12
23
  }
@@ -1,11 +1,14 @@
1
1
  import { validateOutput, hasErrors, formatOutputResults } from '@nera-static/validate'
2
+ import { runJson } from '../json-output.js'
2
3
 
3
4
  // Check the built output in public/ — accessibility, privacy and legal-notice
4
5
  // hints — and print the results. `nera validate` checks the sources; this checks
5
6
  // what the build produced, so it needs a build first: validateOutput throws
6
7
  // "run `nera build` first" when public/ is missing or holds no HTML. Returns the
7
- // exit code (1 on any error, 0 otherwise), like runValidate.
8
- export function runCheck({ cwd = process.cwd() } = {}) {
8
+ // exit code (1 on any error, 0 otherwise), like runValidate. With `json`, it
9
+ // prints one JSON object instead, the error included (see json-output.js).
10
+ export function runCheck({ cwd = process.cwd(), json = false } = {}) {
11
+ if (json) return runJson(() => validateOutput({ cwd }))
9
12
  const results = validateOutput({ cwd })
10
13
  console.log(formatOutputResults(results))
11
14
  return hasErrors(results) ? 1 : 0
@@ -1,9 +1,12 @@
1
1
  import { validateSite, hasErrors, formatResults } from '@nera-static/validate'
2
+ import { runJson } from '../json-output.js'
2
3
 
3
4
  // Validate the current site and print the results. Returns the process exit code
4
5
  // (1 on any error, 0 otherwise) rather than calling process.exit itself, so it
5
- // stays testable; the bin turns the return value into the exit.
6
- export function runValidate({ cwd = process.cwd() } = {}) {
6
+ // stays testable; the bin turns the return value into the exit. With `json`, it
7
+ // prints one `{ ok, results }` object instead (see json-output.js).
8
+ export function runValidate({ cwd = process.cwd(), json = false } = {}) {
9
+ if (json) return runJson(() => validateSite({ cwd }))
7
10
  const results = validateSite({ cwd })
8
11
  console.log(formatResults(results))
9
12
  return hasErrors(results) ? 1 : 0
@@ -0,0 +1,57 @@
1
+ import { hasErrors } from '@nera-static/validate'
2
+
3
+ // `--json` mode, shared by `nera validate`, `nera check` and `nera build`: stdout
4
+ // holds exactly one JSON object and nothing else, so an agent can parse it whole.
5
+ // While `body` runs, console.log/info/debug and raw stdout writes (the engine's
6
+ // build log, plugin output) go to stderr; they are restored afterwards. `body`
7
+ // returns the validator's results, directly or as a promise. The object is
8
+ // `{ ok, results }`, or `{ ok: false, error, results: [] }` when `body` throws —
9
+ // the error is reported here, not rethrown. Returns the exit code, the same one
10
+ // text mode ends with: 1 on any error, 0 otherwise. A sync body keeps the call
11
+ // sync.
12
+ export function runJson(body) {
13
+ const restore = routeLogsToStderr()
14
+ const finish = (value) => {
15
+ restore()
16
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`)
17
+ return value.ok ? 0 : 1
18
+ }
19
+ const report = (results) => ({ ok: !hasErrors(results), results })
20
+ const failure = (error) => ({ ok: false, error: error.message, results: [] })
21
+
22
+ let pending
23
+ try {
24
+ pending = body()
25
+ } catch (error) {
26
+ return finish(failure(error))
27
+ }
28
+ if (typeof pending?.then === 'function') {
29
+ return pending.then(
30
+ (results) => finish(report(results)),
31
+ (error) => finish(failure(error))
32
+ )
33
+ }
34
+ return finish(report(pending))
35
+ }
36
+
37
+ // Point console.log/info/debug and process.stdout.write at stderr. Returns the
38
+ // function that puts the originals back.
39
+ function routeLogsToStderr() {
40
+ const saved = {
41
+ log: console.log,
42
+ info: console.info,
43
+ debug: console.debug,
44
+ write: process.stdout.write,
45
+ }
46
+ const toStderr = (...args) => console.error(...args)
47
+ console.log = toStderr
48
+ console.info = toStderr
49
+ console.debug = toStderr
50
+ process.stdout.write = (...args) => process.stderr.write(...args)
51
+ return () => {
52
+ console.log = saved.log
53
+ console.info = saved.info
54
+ console.debug = saved.debug
55
+ process.stdout.write = saved.write
56
+ }
57
+ }
@@ -8,7 +8,9 @@ read them too.
8
8
  This site is built with [Nera](https://nera.js.org), a static site generator:
9
9
  Markdown pages plus Pug templates in, plain HTML out. Nera is not Eleventy,
10
10
  Hugo, Astro or Jekyll, so do not apply their conventions here. The docs are
11
- at https://nera.js.org.
11
+ at https://nera.js.org; https://nera.js.org/llms.txt indexes them for AI
12
+ assistants, and https://nera.js.org/llms-full.txt holds all of them in one
13
+ Markdown file.
12
14
 
13
15
  ## Folder map
14
16
 
@@ -33,6 +35,10 @@ at https://nera.js.org.
33
35
  - `npx nera build --check` — build, then check the built `public/` for
34
36
  accessibility, privacy and legal issues (`npx nera check` checks without
35
37
  building)
38
+ - `npx nera validate --json`, `npx nera check --json` and
39
+ `npx nera build --check --json` print one JSON object on stdout,
40
+ `{ "ok", "results": [{ file, line, severity, rule, message }] }`, with
41
+ the same exit code; the build log goes to stderr
36
42
 
37
43
  ## After every change
38
44