@rse/specbook 1.2.3 → 1.2.5
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 +124 -204
- package/dst/specbook-cmd-export.js +4 -1
- package/dst/specbook-config.js +6 -6
- package/dst/specbook-diagram.js +154 -22
- package/dst/specbook-export-ast.js +1 -1
- package/dst/specbook-export-common.js +1 -1
- package/dst/specbook-export-html.css +178 -5
- package/dst/specbook-export-html.d.ts +1 -1
- package/dst/specbook-export-html.js +304 -42
- package/dst/specbook-export-md.js +9 -9
- package/dst/specbook-export-pdf.js +37 -13
- package/dst/specbook-format-schema.d.ts +11 -1
- package/dst/specbook-format-schema.js +12 -2
- package/dst/specbook-format-spec.d.ts +1 -1
- package/dst/specbook-format-spec.js +2 -2
- package/dst/specbook-format.md +80 -9
- package/dst/specbook-format.yaml +294 -197
- package/dst/specbook-link.js +2 -2
- package/dst/specbook-parse-semantic.js +22 -22
- package/dst/specbook-parse-syntax.js +5 -5
- package/dst/specbook-parse.js +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -6,6 +6,9 @@ SpecBook
|
|
|
6
6
|
|
|
7
7
|
**Markdown-based Specification Format**
|
|
8
8
|
|
|
9
|
+
[](https://github.com/rse)
|
|
10
|
+
[](https://github.com/rse)
|
|
11
|
+
|
|
9
12
|
About
|
|
10
13
|
-----
|
|
11
14
|
|
|
@@ -43,10 +46,15 @@ CLI with commands `specbook <xxx>`, and an MCP service with tools
|
|
|
43
46
|
- **Strict Validation with Custom Schema**:
|
|
44
47
|
The YAML schema configuration strictly defines the allowed object
|
|
45
48
|
kinds, hierarchies, and properties, whose values are constrained by an
|
|
46
|
-
expression language (regex, enum, tags, list, and reference).
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
expression language (regex, enum, tags, list, and reference). Beyond the
|
|
50
|
+
plain values, it constrains the uniqueness and presence of a property
|
|
51
|
+
among the sibling objects and the shape of a reference-valued property
|
|
52
|
+
(local, symmetric, and/or acyclic), and it can declare the child objects
|
|
53
|
+
of an object kind a finite state machine, whose reachability, dead-ends,
|
|
54
|
+
and livelocks are then checked. Violations are reported as file- and
|
|
55
|
+
line-precise diagnostics, while the reference coverage an object kind
|
|
56
|
+
demands is reported as a warning only. A bundled standard schema
|
|
57
|
+
configuration applies if no particular one is given.
|
|
50
58
|
|
|
51
59
|
- **Object Model Diagram Visualisation**:
|
|
52
60
|
Object kinds can declare "graph", "hub", or "grid" diagrams in the
|
|
@@ -63,26 +71,39 @@ CLI with commands `specbook <xxx>`, and an MCP service with tools
|
|
|
63
71
|
|
|
64
72
|
- **AST Exports for AI/LLMs**:
|
|
65
73
|
The parsed specification Abstract Syntax Tree (AST) can be exported in
|
|
66
|
-
JSON, JSON5, YAML, or TOON format for machine consumption
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
JSON, JSON5, YAML, or TOON format for machine consumption, with the
|
|
75
|
+
derived diagram of an object attached to it as a textual Gradia spec.
|
|
76
|
+
Together with the `describe` command, which explains the models and
|
|
77
|
+
formats, this enables LLMs to both read and write specifications.
|
|
69
78
|
|
|
70
79
|
- **HTML Export for Developers**:
|
|
71
|
-
The HTML export is a self-contained single document with a
|
|
72
|
-
table-of-contents (also as a slide-in side panel for quick navigation),
|
|
73
|
-
full-text search, embedded images, a
|
|
74
|
-
also scrolls back to the top on click),
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
The HTML export is a self-contained single document with a title page,
|
|
81
|
+
a table-of-contents (also as a slide-in side panel for quick navigation),
|
|
82
|
+
a diagram-of-contents, a fuzzy full-text search, embedded images, a
|
|
83
|
+
scroll progress meter (which also scrolls back to the top on click),
|
|
84
|
+
description popups (which show the schema and corpus description of
|
|
85
|
+
an object kind, property, or object instance on hover), and a
|
|
86
|
+
light/dark theme toggle. It is intended for the day-to-day online
|
|
87
|
+
reading during development. The HTML export can map all object kinds
|
|
88
|
+
to nested sections or compact tables, or an automatic mixture which
|
|
89
|
+
collapses only the deepest level into tables. The HTML export even can
|
|
90
|
+
be previewed in the browser with live updates.
|
|
80
91
|
|
|
81
92
|
- **PDF Export for Customers**:
|
|
82
93
|
The PDF export prints the HTML rendering via Chromium and post-processes
|
|
83
|
-
it with page numbers, headers/footers, and a hierarchical
|
|
84
|
-
|
|
85
|
-
|
|
94
|
+
it with page numbers, headers/footers, a brand bar, and a hierarchical
|
|
95
|
+
PDF outline. The paper size (A4, Letter, or Legal) drives the pagination
|
|
96
|
+
and scales the diagrams down to fit onto a single page. It is intended
|
|
97
|
+
as a polished, paginated offline document for handing over to customers.
|
|
98
|
+
|
|
99
|
+
- **Document Theming and Typography**:
|
|
100
|
+
The title object of the specification drives the entire document:
|
|
101
|
+
its title, subtitle, author, version, and logo fill the title page
|
|
102
|
+
(with an optional light/dark variant of the logo), its language
|
|
103
|
+
selects the smart typography quote style, its color tone seeds the
|
|
104
|
+
theme color spreads of both the light and the dark theme, and its
|
|
105
|
+
character set subsets the embedded fonts down to the actually needed
|
|
106
|
+
glyphs.
|
|
86
107
|
|
|
87
108
|
Overview
|
|
88
109
|
--------
|
|
@@ -101,170 +122,6 @@ $ npm install -g @rse/specbook
|
|
|
101
122
|
Usage
|
|
102
123
|
-----
|
|
103
124
|
|
|
104
|
-
### API
|
|
105
|
-
|
|
106
|
-
```ts
|
|
107
|
-
/* the SpecBook API, providing all commands the CLI and MCP service expose */
|
|
108
|
-
export class SpecBook {
|
|
109
|
-
constructor (options?: SpecBookOptions)
|
|
110
|
-
|
|
111
|
-
/* initialize the configured specification artifact files below the
|
|
112
|
-
base directory and return the list of the generated files */
|
|
113
|
-
init (options: {
|
|
114
|
-
config?: string[], /* YAML schema configuration files or glob patterns, merged
|
|
115
|
-
in order (default: bundled standard one, named "std") */
|
|
116
|
-
basedir?: string /* base directory (default: ".") */
|
|
117
|
-
}): Promise<string[]>
|
|
118
|
-
|
|
119
|
-
/* parse and validate the specification Markdown files below the base directory */
|
|
120
|
-
lint (options: {
|
|
121
|
-
config?: string[],
|
|
122
|
-
basedir?: string
|
|
123
|
-
}): Promise<LintResult>
|
|
124
|
-
|
|
125
|
-
/* export the specification, parsing the input just once and
|
|
126
|
-
returning one buffer per requested format (strict: any error
|
|
127
|
-
diagnostic rejects the export with an "Error", while the warnings
|
|
128
|
-
are surfaced as "notice" verbose messages) */
|
|
129
|
-
export (options: {
|
|
130
|
-
config?: string[],
|
|
131
|
-
basedir?: string,
|
|
132
|
-
formats?: ExportFormat[], /* requested output formats (default: [ "json" ]) */
|
|
133
|
-
realtime?: boolean /* inject the live preview script into the HTML */
|
|
134
|
-
}): Promise<Buffer[]>
|
|
135
|
-
|
|
136
|
-
/* export the specification like "export" and then keep it in sync by
|
|
137
|
-
re-exporting it on every change of a specification file (a failed
|
|
138
|
-
re-export is reported and leaves the observe loop intact) */
|
|
139
|
-
watch (options: {
|
|
140
|
-
config?: string[],
|
|
141
|
-
basedir?: string,
|
|
142
|
-
formats?: ExportFormat[],
|
|
143
|
-
realtime?: boolean,
|
|
144
|
-
outputs?: string[], /* paths "onExport" writes, refused if observed */
|
|
145
|
-
onExport: (buffers: Buffer[]) => void | Promise<void>
|
|
146
|
-
}): Promise<void>
|
|
147
|
-
|
|
148
|
-
/* serve the HTML export as a live preview on "http://<addr>:<port>/",
|
|
149
|
-
kept in sync like "watch" and updated in the browser on every change */
|
|
150
|
-
preview (options: {
|
|
151
|
-
config?: string[],
|
|
152
|
-
basedir?: string,
|
|
153
|
-
addr?: string, /* listening IP address (default: "127.0.0.1") */
|
|
154
|
-
port?: number /* listening TCP port (default: 12345) */
|
|
155
|
-
}): Promise<void>
|
|
156
|
-
|
|
157
|
-
/* describe the SpecBook models and formats, optionally pointing to
|
|
158
|
-
the artifacts of the particular project */
|
|
159
|
-
describe (options: {
|
|
160
|
-
config?: string[],
|
|
161
|
-
basedir?: string,
|
|
162
|
-
embed?: boolean, /* embed the schema configuration instead of referencing it */
|
|
163
|
-
compress?: CompressLevel, /* compression level of the schema configuration (default: 1) */
|
|
164
|
-
format?: DescribeFormat, /* output format (default: "md") */
|
|
165
|
-
part?: DescribePart /* document part (default: "all") */
|
|
166
|
-
}): Promise<string>
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/* the constructor options and the sink of the verbose messages,
|
|
170
|
-
receiving the emitting command and the message */
|
|
171
|
-
export interface SpecBookOptions {
|
|
172
|
-
verbose?: VerboseSink
|
|
173
|
-
}
|
|
174
|
-
export type VerboseSink = (cmd: string, msg: string) => void
|
|
175
|
-
|
|
176
|
-
/* the result of the "lint" command and its file- and line-precise diagnostics */
|
|
177
|
-
export interface LintResult {
|
|
178
|
-
specification: Spec
|
|
179
|
-
diagnostics: Diagnostic[]
|
|
180
|
-
config?: Schema
|
|
181
|
-
files: string[] /* the artifact files plus their embedded assets */
|
|
182
|
-
}
|
|
183
|
-
export interface Diagnostic {
|
|
184
|
-
file: string
|
|
185
|
-
line: number
|
|
186
|
-
column: number
|
|
187
|
-
severity: "error" | "warning"
|
|
188
|
-
message: string
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/* the supported export formats and describe formats/parts/compression levels */
|
|
192
|
-
export const formats: readonly [ "json", "json5", "yaml", "toon", "html", "pdf", "md" ]
|
|
193
|
-
export const describeFormats: readonly [ "md", "raw" ]
|
|
194
|
-
export const describeParts: readonly [ "all", "meta", "schema", "spec" ]
|
|
195
|
-
export const compressLevels: readonly [ 0, 1, 2, 3 ]
|
|
196
|
-
export type ExportFormat = typeof formats[number]
|
|
197
|
-
export type DescribeFormat = typeof describeFormats[number]
|
|
198
|
-
export type DescribePart = typeof describeParts[number]
|
|
199
|
-
export type CompressLevel = typeof compressLevels[number]
|
|
200
|
-
|
|
201
|
-
/* the Abstract Syntax Tree (AST) of a parsed specification */
|
|
202
|
-
export type Spec = {
|
|
203
|
-
artifacts: SpecArtifact[]
|
|
204
|
-
}
|
|
205
|
-
export type SpecArtifact = {
|
|
206
|
-
created: Date
|
|
207
|
-
modified: Date
|
|
208
|
-
objects: SpecObject[]
|
|
209
|
-
}
|
|
210
|
-
export type SpecObject = {
|
|
211
|
-
kind: string
|
|
212
|
-
id: string
|
|
213
|
-
anchor?: string
|
|
214
|
-
paren?: string
|
|
215
|
-
name: string
|
|
216
|
-
primary?: boolean
|
|
217
|
-
description?: SpecDescription
|
|
218
|
-
properties: SpecProperty[]
|
|
219
|
-
childs: SpecObject[]
|
|
220
|
-
}
|
|
221
|
-
export type SpecDescription = {
|
|
222
|
-
description: string
|
|
223
|
-
rationale?: string
|
|
224
|
-
embedding?: string[]
|
|
225
|
-
}
|
|
226
|
-
export type SpecProperty = {
|
|
227
|
-
key: string
|
|
228
|
-
value: string
|
|
229
|
-
embedding?: string[]
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/* the YAML schema configuration, where the nested "diagram" and
|
|
233
|
-
"format" structures are detailed in the "describe" output */
|
|
234
|
-
export type Schema = SchemaObject[]
|
|
235
|
-
export type SchemaObject = {
|
|
236
|
-
kind: string
|
|
237
|
-
name?: string
|
|
238
|
-
id?: string
|
|
239
|
-
file?: string
|
|
240
|
-
desc?: string
|
|
241
|
-
optional?: boolean
|
|
242
|
-
diagram?: SchemaDiagram
|
|
243
|
-
format?: SchemaFormat
|
|
244
|
-
props?: SchemaProperty[]
|
|
245
|
-
childs?: SchemaObject[]
|
|
246
|
-
}
|
|
247
|
-
export type SchemaProperty = {
|
|
248
|
-
name: string
|
|
249
|
-
desc?: string
|
|
250
|
-
value?: string
|
|
251
|
-
optional?: boolean
|
|
252
|
-
}
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
Example:
|
|
256
|
-
|
|
257
|
-
```ts
|
|
258
|
-
import { SpecBook } from "@rse/specbook"
|
|
259
|
-
|
|
260
|
-
const specbook = new SpecBook({
|
|
261
|
-
verbose: (cmd, msg) => console.error(`specbook: ${cmd}: ${msg}`)
|
|
262
|
-
})
|
|
263
|
-
const result = await specbook.lint({
|
|
264
|
-
basedir: "smp/broadcast"
|
|
265
|
-
})
|
|
266
|
-
```
|
|
267
|
-
|
|
268
125
|
### CLI
|
|
269
126
|
|
|
270
127
|
Commands:
|
|
@@ -354,13 +211,14 @@ Options:
|
|
|
354
211
|
|
|
355
212
|
- `-w|--watch` (`export` only):
|
|
356
213
|
Keep the outputs in sync with their sources: after the regular export,
|
|
357
|
-
the
|
|
358
|
-
and every change re-exports the
|
|
359
|
-
silent for one second. A failed
|
|
360
|
-
observe loop intact, so a
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
214
|
+
the YAML schema configuration files, the referenced artifact files, and
|
|
215
|
+
all assets they embed are observed, and every change re-exports the
|
|
216
|
+
specification once the sources stayed silent for one second. A failed
|
|
217
|
+
re-export is reported and leaves the observe loop intact, so a
|
|
218
|
+
transiently invalid specification (or configuration) does not end the
|
|
219
|
+
watch. The outputs have to be regular files, as `-` (stdout) cannot
|
|
220
|
+
receive a repeated export, and none of them may be an observed source
|
|
221
|
+
file itself, as its own write would re-trigger the observation
|
|
364
222
|
endlessly.
|
|
365
223
|
|
|
366
224
|
- `-a|--addr <ip-addr>`, `-p|--port <tcp-port>` (`preview` only):
|
|
@@ -369,11 +227,13 @@ Options:
|
|
|
369
227
|
`http://<ip-addr>:<tcp-port>/`, kept in sync with its sources exactly
|
|
370
228
|
like `export --watch`, and updated in the browser after every change
|
|
371
229
|
through a WebSocket connection the served page keeps open, where the
|
|
372
|
-
document is replaced in place, so the scroll position
|
|
373
|
-
failed re-export keeps the previous HTML in place
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
230
|
+
document is replaced in place, so the scroll position and the theme
|
|
231
|
+
choice survive (a failed re-export keeps the previous HTML in place,
|
|
232
|
+
and a request before the first successful export is answered with a
|
|
233
|
+
placeholder page which replaces itself with the document once that
|
|
234
|
+
export arrives). A status tab at the bottom of the brand bar shows
|
|
235
|
+
the connection state: its plug icon carries the search highlight
|
|
236
|
+
color while disconnected and blinks for 2s after every update.
|
|
377
237
|
|
|
378
238
|
- `-o|--output <output-file>` (`describe` only):
|
|
379
239
|
The output file (default: `-` for stdout) receives the described
|
|
@@ -413,26 +273,86 @@ explicitly supplied option always wins. As `-c|--config` is repeatable,
|
|
|
413
273
|
`SPECBOOK_CONFIG` carries a list of patterns separated by the path
|
|
414
274
|
delimiter of the platform (`:` on Unix, `;` on Windows).
|
|
415
275
|
|
|
416
|
-
|
|
276
|
+
Beyond those, the option-less `SPECBOOK_BROWSER` selects the browser
|
|
277
|
+
printing the PDF export: a value carrying a path separator is taken as
|
|
278
|
+
an executable path and any other one as a Playwright channel name
|
|
279
|
+
(`chromium`, `chromium-headless-shell`, `chrome`, `chrome-beta`,
|
|
280
|
+
`chrome-dev`, `chrome-canary`, `msedge`, `msedge-beta`, `msedge-dev`,
|
|
281
|
+
or `msedge-canary`). The variable itself has no default value: when it
|
|
282
|
+
is unset, the downloaded Playwright Chromium is used (the equivalent of
|
|
283
|
+
`chromium`) and only if that one is absent a system-installed *Google
|
|
284
|
+
Chrome* (the equivalent of `chrome`). An explicitly configured browser
|
|
285
|
+
failing to launch fails the export instead of falling back onto another
|
|
286
|
+
browser.
|
|
287
|
+
|
|
288
|
+
Example: Simple
|
|
289
|
+
---------------
|
|
290
|
+
|
|
291
|
+
Check out the *simple* specification of simple
|
|
292
|
+
example data model:
|
|
293
|
+
|
|
294
|
+
### Sources
|
|
295
|
+
|
|
296
|
+
- [Specification](smp/sample/sample.md)
|
|
297
|
+
- [Schema](smp/sample/sample.yaml)
|
|
298
|
+
|
|
299
|
+
### Generation
|
|
417
300
|
|
|
418
301
|
```bash
|
|
419
|
-
$ specbook lint -v
|
|
302
|
+
$ specbook lint -v \
|
|
303
|
+
-b smp/sample \
|
|
304
|
+
-o smp/sample/sample.html \
|
|
305
|
+
-o smp/sample/sample.pdf
|
|
420
306
|
```
|
|
421
307
|
|
|
422
|
-
Rendering
|
|
423
|
-
|
|
308
|
+
### HTML Rendering
|
|
309
|
+
|
|
310
|
+

|
|
311
|
+

|
|
424
312
|
|
|
425
|
-
|
|
313
|
+
Example: Complex
|
|
314
|
+
----------------
|
|
426
315
|
|
|
427
|
-
|
|
316
|
+
Check out the *complex* specification of the Broadcast
|
|
317
|
+
application, based on **SpecBook**'s built-in "standard" schema:
|
|
428
318
|
|
|
429
|
-
###
|
|
319
|
+
### Sources
|
|
430
320
|
|
|
431
|
-
|
|
321
|
+
- [Specification](smp/broadcast/)
|
|
322
|
+
- [Schema](src/specbook-format.d/)
|
|
323
|
+
|
|
324
|
+
### Generation
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
$ specbook lint -v \
|
|
328
|
+
-b smp/broadcast \
|
|
329
|
+
-o smp/broadcast/broadcast.html \
|
|
330
|
+
-o smp/broadcast/broadcast.pdf
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### HTML Rendering
|
|
334
|
+
|
|
335
|
+

|
|
336
|
+

|
|
432
337
|
|
|
433
338
|
### PDF Rendering
|
|
434
339
|
|
|
435
|
-

|
|
340
|
+

|
|
341
|
+
|
|
342
|
+
See Also
|
|
343
|
+
--------
|
|
344
|
+
|
|
345
|
+
- [Gradia](https://github.com/rse/gradia)
|
|
346
|
+
- [ASE](https://ase.tools)
|
|
347
|
+
|
|
348
|
+
Support
|
|
349
|
+
-------
|
|
350
|
+
|
|
351
|
+
**SpecBook** is developed in the experience context of industrial Software
|
|
352
|
+
Engineering at the [*msg group*](https://www.msg.group) and in the
|
|
353
|
+
educational context of the *Software Engineering Academy (SEA)*. **SpecBook**
|
|
354
|
+
development is supported by *msg Research* and *Software Engineering
|
|
355
|
+
Academy (SEA)*.
|
|
436
356
|
|
|
437
357
|
License
|
|
438
358
|
-------
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
** Copyright (c) 2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
4
|
** Licensed under Apache 2.0 <https://spdx.org/licenses/Apache-2.0>
|
|
5
5
|
*/
|
|
6
|
-
|
|
6
|
+
/* Chokidar is aliased in "package.json", as Nunjucks declares the
|
|
7
|
+
(unused) Chokidar 3 as an optional peer dependency and the current
|
|
8
|
+
Chokidar under its plain name would raise a peer warning on install */
|
|
9
|
+
import { watch } from "chokidar5";
|
|
7
10
|
import { minify } from "@swc/html";
|
|
8
11
|
import { documentTitle, documentLogo, documentCharset, documentThemeTone, subsetStylesheet, documentPaperSize, paperStylesheet, charsetCodepoints } from "./specbook-export-common.js";
|
|
9
12
|
import { themeColors, themeStylesheet, themeMapping } from "./specbook-theme.js";
|
package/dst/specbook-config.js
CHANGED
|
@@ -33,7 +33,7 @@ const identityOf = (item, list) => {
|
|
|
33
33
|
const object = item;
|
|
34
34
|
if (list === "props")
|
|
35
35
|
return String(object.name ?? "");
|
|
36
|
-
else if (list === "
|
|
36
|
+
else if (list === "children")
|
|
37
37
|
return String(object.kind ?? "");
|
|
38
38
|
else
|
|
39
39
|
return `${String(object.kind ?? "")}:${String(object.id ?? object.name ?? "")}`;
|
|
@@ -158,7 +158,7 @@ const checkAutomaton = (object, at, diagnose) => {
|
|
|
158
158
|
if (object.automaton === undefined)
|
|
159
159
|
return;
|
|
160
160
|
const automaton = object.automaton;
|
|
161
|
-
const childOf = (kind) => (object.
|
|
161
|
+
const childOf = (kind) => (object.children ?? []).find((c) => c.kind === kind);
|
|
162
162
|
const propOf = (child, name) => (child?.props ?? []).find((p) => p.name === name);
|
|
163
163
|
/* an invalid value expression is reported on its own (see
|
|
164
164
|
"checkProperties"), so it must not cascade into a shape diagnostic */
|
|
@@ -207,7 +207,7 @@ const checkConstraints = (config, posOfPath) => {
|
|
|
207
207
|
const seen = new Set();
|
|
208
208
|
for (const [i, object] of objects.entries()) {
|
|
209
209
|
const at = [...path, i];
|
|
210
|
-
const ident = identityOf(object, depth > 1 ? "
|
|
210
|
+
const ident = identityOf(object, depth > 1 ? "children" : undefined);
|
|
211
211
|
if (seen.has(ident))
|
|
212
212
|
diagnose([...at, "kind"], `object "${ident}" collides with a preceding sibling object (the later one is unreachable)`);
|
|
213
213
|
else
|
|
@@ -239,8 +239,8 @@ const checkConstraints = (config, posOfPath) => {
|
|
|
239
239
|
(err instanceof Error ? err.message : String(err)));
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
|
-
if (object.
|
|
243
|
-
check(object.
|
|
242
|
+
if (object.children !== undefined)
|
|
243
|
+
check(object.children, [...at, "children"], depth + 1);
|
|
244
244
|
}
|
|
245
245
|
};
|
|
246
246
|
check(config, [], 1);
|
|
@@ -258,7 +258,7 @@ export const configStatistics = (config) => {
|
|
|
258
258
|
for (const prop of object.props ?? [])
|
|
259
259
|
if (prop.value !== undefined && admitsReferences(compileValueExpr(prop.value)))
|
|
260
260
|
links++;
|
|
261
|
-
for (const child of object.
|
|
261
|
+
for (const child of object.children ?? [])
|
|
262
262
|
walk(child);
|
|
263
263
|
};
|
|
264
264
|
for (const object of config)
|