@rse/specbook 0.9.2 → 0.9.4
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 +195 -14
- package/dst/specbook-api.d.ts +1 -1
- package/dst/specbook-api.js +11 -2
- package/dst/specbook-cli.js +15 -16
- package/dst/specbook-cmd-describe.js +3 -1
- package/dst/specbook-cmd-edit.js +6 -1
- package/dst/specbook-cmd-export.d.ts +1 -1
- package/dst/specbook-cmd-export.js +6 -5
- package/dst/specbook-cmd-import.js +6 -1
- package/dst/specbook-cmd-init.js +4 -2
- package/dst/specbook-cmd-lint.js +4 -2
- package/dst/specbook-config.js +22 -7
- package/dst/specbook-diagram.js +67 -43
- package/dst/specbook-export-ast.js +5 -2
- package/dst/specbook-export-common.d.ts +0 -1
- package/dst/specbook-export-common.js +32 -19
- package/dst/specbook-export-html-search.js +6 -4
- package/dst/specbook-export-html.css +21 -0
- package/dst/specbook-export-html.d.ts +2 -2
- package/dst/specbook-export-html.js +218 -70
- package/dst/specbook-export-md.js +10 -6
- package/dst/specbook-export-pdf.js +4 -3
- package/dst/specbook-llm.js +18 -28
- package/dst/specbook-mcp.js +4 -6
- package/dst/specbook-parse-semantic.js +73 -58
- package/dst/specbook-parse-syntax.js +17 -16
- package/dst/specbook-parse-value.js +7 -1
- package/dst/specbook-parse.js +4 -0
- package/dst/specbook-struct-schema.d.ts +7 -0
- package/dst/specbook-struct-schema.js +10 -3
- package/dst/specbook-theme.js +1 -1
- package/package.json +4 -4
|
@@ -50,22 +50,12 @@ const matchAlternatives = (ctx, alternatives, item) => {
|
|
|
50
50
|
}
|
|
51
51
|
return false;
|
|
52
52
|
};
|
|
53
|
-
/*
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (level > 1 && schema.name !== undefined && !anchored(schema.name).test(plainText(object.name)))
|
|
60
|
-
ctx.diagnose(meta.file, meta.line, `${object.kind} name "${object.name}" does not match pattern "${schema.name}"`);
|
|
61
|
-
/* a configured id has to be explicitly specified 1:1 in the
|
|
62
|
-
input, via either "{{<id>}}" or "(<id>)" */
|
|
63
|
-
if (schema.id !== undefined && (object.anchor ?? object.paren) !== schema.id)
|
|
64
|
-
ctx.diagnose(meta.file, meta.line, `configured id "${schema.id}" not explicitly specified on ${object.kind} "${object.name}"`);
|
|
65
|
-
/* find a property by its key (case-sensitive) */
|
|
66
|
-
const findProp = (name) => object.properties.find((p) => plainKey(p.key) === name);
|
|
67
|
-
/* the tokens split off multi-token property values below, assigned
|
|
68
|
-
to the still unset configured properties by matching their patterns */
|
|
53
|
+
/* find a property of an object by its key (case-sensitive) */
|
|
54
|
+
const findProp = (object, name) => object.properties.find((p) => plainKey(p.key) === name);
|
|
55
|
+
/* distribute the inline tokens split off the multi-token property values
|
|
56
|
+
of an object across its configured properties and report the
|
|
57
|
+
unassignable ones (the properties assigned a token are returned) */
|
|
58
|
+
const assignInlineTokens = (ctx, object, props, meta) => {
|
|
69
59
|
const tokens = new Array();
|
|
70
60
|
const tokenAssigned = new Set();
|
|
71
61
|
/* explode a multi-token property value whose direct pattern check
|
|
@@ -77,7 +67,7 @@ const validateObject = (ctx, object, schema, level) => {
|
|
|
77
67
|
const expr = compileValueExpr(prop.value);
|
|
78
68
|
if (expr.kind !== "regex" && expr.kind !== "enum")
|
|
79
69
|
continue;
|
|
80
|
-
const match = findProp(prop.name);
|
|
70
|
+
const match = findProp(object, prop.name);
|
|
81
71
|
if (match === undefined || directMatches(expr, plainText(match.value)))
|
|
82
72
|
continue;
|
|
83
73
|
const parts = plainText(match.value).split(/\s+/);
|
|
@@ -91,8 +81,10 @@ const validateObject = (ctx, object, schema, level) => {
|
|
|
91
81
|
parts.splice(idx, 1);
|
|
92
82
|
tokens.push(...parts);
|
|
93
83
|
}
|
|
84
|
+
/* assign the collected tokens to the still unset configured
|
|
85
|
+
properties whose pattern they match */
|
|
94
86
|
for (const prop of props) {
|
|
95
|
-
if (prop.value === undefined || findProp(prop.name) !== undefined)
|
|
87
|
+
if (prop.value === undefined || findProp(object, prop.name) !== undefined)
|
|
96
88
|
continue;
|
|
97
89
|
const expr = compileValueExpr(prop.value);
|
|
98
90
|
if (expr.kind !== "regex" && expr.kind !== "enum")
|
|
@@ -104,13 +96,69 @@ const validateObject = (ctx, object, schema, level) => {
|
|
|
104
96
|
tokens.splice(idx, 1);
|
|
105
97
|
}
|
|
106
98
|
}
|
|
99
|
+
/* report the tokens which no configured property accepts */
|
|
107
100
|
for (const token of tokens)
|
|
108
101
|
ctx.diagnose(meta.file, meta.line, `unassignable inline token "${token}" on ${object.kind} "${object.name}"`);
|
|
102
|
+
return tokenAssigned;
|
|
103
|
+
};
|
|
104
|
+
/* check a property value against the compiled value expression of its
|
|
105
|
+
configured schema property */
|
|
106
|
+
const checkPropValue = (ctx, prop, expr, property, meta) => {
|
|
107
|
+
if (expr.kind === "reference") {
|
|
108
|
+
/* a reference constraint: the value has to be exactly one
|
|
109
|
+
reference resolving into the constraint's wildcard match set */
|
|
110
|
+
if (!/^\[\[[^[\]]+\]\]$/.test(property.value.trim()))
|
|
111
|
+
ctx.diagnose(meta.file, meta.line, `property "${prop.name}" value "${property.value}" is not a single link reference`);
|
|
112
|
+
else {
|
|
113
|
+
const ref = property.value.trim().slice(2, -2).trim();
|
|
114
|
+
const target = resolveUnique(ctx.linkIndex, ref).target;
|
|
115
|
+
if (target !== undefined && !resolveSet(ctx.linkIndex, expr.pattern).includes(target))
|
|
116
|
+
ctx.diagnose(meta.file, meta.line, `link reference "[[${ref}]]" does not match constraint "${prop.value}"`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (expr.kind === "tags") {
|
|
120
|
+
/* a tags constraint: the value is a comma-separated set of
|
|
121
|
+
configured tags, each occurring at most once */
|
|
122
|
+
const seen = new Set();
|
|
123
|
+
for (const item of splitItems(plainText(property.value))) {
|
|
124
|
+
if (!expr.members.includes(item))
|
|
125
|
+
ctx.diagnose(meta.file, meta.line, `tag "${item}" of property "${prop.name}" not allowed by constraint "${prop.value}"`);
|
|
126
|
+
else if (seen.has(item))
|
|
127
|
+
ctx.diagnose(meta.file, meta.line, `duplicate tag "${item}" on property "${prop.name}"`);
|
|
128
|
+
seen.add(item);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else if (expr.kind === "list") {
|
|
132
|
+
/* a list constraint: the value is a comma-separated list
|
|
133
|
+
of items, each matching at least one alternative */
|
|
134
|
+
for (const item of splitItems(property.value))
|
|
135
|
+
if (!matchAlternatives(ctx, expr.alternatives, item))
|
|
136
|
+
ctx.diagnose(meta.file, meta.line, `list item "${item}" of property "${prop.name}" does not match constraint "${prop.value}"`);
|
|
137
|
+
}
|
|
138
|
+
else if (!directMatches(expr, plainText(property.value)))
|
|
139
|
+
ctx.diagnose(meta.file, meta.line, expr.kind === "enum" ?
|
|
140
|
+
`property "${prop.name}" value "${property.value}" is not a member of "${prop.value}"` :
|
|
141
|
+
`property "${prop.name}" value "${property.value}" does not match pattern "${prop.value}"`);
|
|
142
|
+
};
|
|
143
|
+
/* validate a single object (and recursively its childs) against its schema */
|
|
144
|
+
const validateObject = (ctx, object, schema, level) => {
|
|
145
|
+
const meta = ctx.objectMeta.get(object) ?? { file: "", line: 1 };
|
|
146
|
+
const props = schema.props ?? [];
|
|
147
|
+
/* check the name convention (the configured name of non-artifact
|
|
148
|
+
objects is a regular expression pattern) */
|
|
149
|
+
if (level > 1 && schema.name !== undefined && !anchored(schema.name).test(plainText(object.name)))
|
|
150
|
+
ctx.diagnose(meta.file, meta.line, `${object.kind} name "${object.name}" does not match pattern "${schema.name}"`);
|
|
151
|
+
/* a configured id has to be explicitly specified 1:1 in the
|
|
152
|
+
input, via either "{{<id>}}" or "(<id>)" */
|
|
153
|
+
if (schema.id !== undefined && (object.anchor ?? object.paren) !== schema.id)
|
|
154
|
+
ctx.diagnose(meta.file, meta.line, `configured id "${schema.id}" not explicitly specified on ${object.kind} "${object.name}"`);
|
|
155
|
+
/* distribute the inline tokens split off the multi-token property values */
|
|
156
|
+
const tokenAssigned = assignInlineTokens(ctx, object, props, meta);
|
|
109
157
|
/* check the configured properties */
|
|
110
158
|
let parenConsumed = false;
|
|
111
159
|
for (const prop of props) {
|
|
112
160
|
const expr = prop.value !== undefined ? compileValueExpr(prop.value) : undefined;
|
|
113
|
-
const match = findProp(prop.name);
|
|
161
|
+
const match = findProp(object, prop.name);
|
|
114
162
|
if (match === undefined) {
|
|
115
163
|
/* accept a trailing parenthesized name token as the value of a
|
|
116
164
|
still missing property when it matches the property pattern */
|
|
@@ -122,45 +170,10 @@ const validateObject = (ctx, object, schema, level) => {
|
|
|
122
170
|
if (prop.optional !== true)
|
|
123
171
|
ctx.diagnose(meta.file, meta.line, `required property "${prop.name}" missing on ${object.kind} "${object.name}"`);
|
|
124
172
|
}
|
|
125
|
-
else if (expr !== undefined)
|
|
126
|
-
|
|
127
|
-
if (expr.kind === "reference") {
|
|
128
|
-
/* a reference constraint: the value has to be exactly one
|
|
129
|
-
reference resolving into the constraint's wildcard match set */
|
|
130
|
-
if (!/^\[\[[^[\]]+\]\]$/.test(match.value.trim()))
|
|
131
|
-
ctx.diagnose(meta.file, line, `property "${prop.name}" value "${match.value}" is not a single link reference`);
|
|
132
|
-
else {
|
|
133
|
-
const ref = match.value.trim().slice(2, -2).trim();
|
|
134
|
-
const target = resolveUnique(ctx.linkIndex, ref).target;
|
|
135
|
-
if (target !== undefined && !resolveSet(ctx.linkIndex, expr.pattern).includes(target))
|
|
136
|
-
ctx.diagnose(meta.file, line, `link reference "[[${ref}]]" does not match constraint "${prop.value}"`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
else if (expr.kind === "tags") {
|
|
140
|
-
/* a tags constraint: the value is a comma-separated set of
|
|
141
|
-
configured tags, each occurring at most once */
|
|
142
|
-
const seen = new Set();
|
|
143
|
-
for (const item of splitItems(plainText(match.value))) {
|
|
144
|
-
if (!expr.members.includes(item))
|
|
145
|
-
ctx.diagnose(meta.file, line, `tag "${item}" of property "${prop.name}" not allowed by constraint "${prop.value}"`);
|
|
146
|
-
else if (seen.has(item))
|
|
147
|
-
ctx.diagnose(meta.file, line, `duplicate tag "${item}" on property "${prop.name}"`);
|
|
148
|
-
seen.add(item);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
else if (expr.kind === "list") {
|
|
152
|
-
/* a list constraint: the value is a comma-separated list
|
|
153
|
-
of items, each matching at least one alternative */
|
|
154
|
-
for (const item of splitItems(match.value))
|
|
155
|
-
if (!matchAlternatives(ctx, expr.alternatives, item))
|
|
156
|
-
ctx.diagnose(meta.file, line, `list item "${item}" of property "${prop.name}" does not match constraint "${prop.value}"`);
|
|
157
|
-
}
|
|
158
|
-
else if (!tokenAssigned.has(prop.name) && !directMatches(expr, plainText(match.value)))
|
|
159
|
-
ctx.diagnose(meta.file, line, expr.kind === "enum" ?
|
|
160
|
-
`property "${prop.name}" value "${match.value}" is not a member of "${prop.value}"` :
|
|
161
|
-
`property "${prop.name}" value "${match.value}" does not match pattern "${prop.value}"`);
|
|
162
|
-
}
|
|
173
|
+
else if (expr !== undefined && !tokenAssigned.has(prop.name))
|
|
174
|
+
checkPropValue(ctx, prop, expr, match, { file: meta.file, line: ctx.propMeta.get(match)?.line ?? meta.line });
|
|
163
175
|
}
|
|
176
|
+
/* report the properties not configured by the schema */
|
|
164
177
|
for (const property of object.properties)
|
|
165
178
|
if (!props.some((p) => p.name === plainKey(property.key)))
|
|
166
179
|
ctx.diagnose(meta.file, ctx.propMeta.get(property)?.line ?? meta.line, `unknown property "${property.key}" on ${object.kind} "${object.name}"`);
|
|
@@ -179,6 +192,7 @@ const validateObject = (ctx, object, schema, level) => {
|
|
|
179
192
|
}
|
|
180
193
|
validateObject(ctx, child, childSchema, level + 1);
|
|
181
194
|
}
|
|
195
|
+
/* report the configured child object kinds which are missing */
|
|
182
196
|
for (const child of childs)
|
|
183
197
|
if (child.optional !== true
|
|
184
198
|
&& !object.childs.some((c) => c.kind === child.kind))
|
|
@@ -204,7 +218,8 @@ export const validate = (ctx, specification, config) => {
|
|
|
204
218
|
for (const object of artifact.objects) {
|
|
205
219
|
const meta = ctx.objectMeta.get(object) ?? { file: "", line: 1 };
|
|
206
220
|
const schema = config.find((s) => (s.kind === object.kind && s.id === object.id) || `${s.kind}-${s.id}` === object.id) ??
|
|
207
|
-
config.find((s) =>
|
|
221
|
+
config.find((s) => s.name !== undefined
|
|
222
|
+
&& s.name.toUpperCase() === plainText(object.name).toUpperCase());
|
|
208
223
|
if (schema === undefined) {
|
|
209
224
|
ctx.diagnose(meta.file, meta.line, `unknown artifact "${object.kind}: ${object.name}" (id "${object.id}")`);
|
|
210
225
|
continue;
|
|
@@ -3,8 +3,8 @@
|
|
|
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
|
-
import fs from "node:fs";
|
|
7
|
-
import path from "node:path";
|
|
6
|
+
import * as fs from "node:fs";
|
|
7
|
+
import * as path from "node:path";
|
|
8
8
|
import { marked } from "marked";
|
|
9
9
|
import { embeddingRegex, embeddingMimeType } from "./specbook-parse-common.js";
|
|
10
10
|
/* split a description text into statement and optional rationale */
|
|
@@ -89,7 +89,7 @@ const parseFrontmatter = (text) => {
|
|
|
89
89
|
const line = 2 + (m[1].slice(0, km.index).match(/\n/g) ?? []).length;
|
|
90
90
|
const value = km[1].trim();
|
|
91
91
|
const date = new Date(value);
|
|
92
|
-
return { date: isNaN(date.getTime()) ? null : date, line, value };
|
|
92
|
+
return { date: Number.isNaN(date.getTime()) ? null : date, line, value };
|
|
93
93
|
};
|
|
94
94
|
return {
|
|
95
95
|
present: true,
|
|
@@ -137,8 +137,7 @@ const parseList = (ctx, list, object, file, line) => {
|
|
|
137
137
|
/* a concise-format item carries a further segment on its first
|
|
138
138
|
line, or ends it with the segment terminator (the case of an
|
|
139
139
|
object without properties, whose description follows below) */
|
|
140
|
-
|
|
141
|
-
if (cm !== null)
|
|
140
|
+
if (/^[^:;]+:\s+.+;.*$/.test(first))
|
|
142
141
|
parseConcise(ctx, item, object, file, line);
|
|
143
142
|
else {
|
|
144
143
|
const km = first.match(/^([^:;]+):\s*(.*)$/);
|
|
@@ -234,7 +233,7 @@ const parseConcise = (ctx, item, parent, file, line, group) => {
|
|
|
234
233
|
const statements = new Array();
|
|
235
234
|
for (const segment of segments) {
|
|
236
235
|
const km = segment.match(/^([^:;]+):\s+(.+)$/);
|
|
237
|
-
if (km !== null && !(/\
|
|
236
|
+
if (km !== null && !(/\s(?:\*\*BECAUSE\*\*|BECAUSE)\s/.test(segment))) {
|
|
238
237
|
const property = { key: km[1].trim(), value: km[2].trim() };
|
|
239
238
|
ctx.propMeta.set(property, { line });
|
|
240
239
|
object.properties.push(property);
|
|
@@ -261,6 +260,7 @@ export const parseFile = (ctx, source) => {
|
|
|
261
260
|
ctx.diagnose(source.file, info.line, info.value === null ?
|
|
262
261
|
`missing "${key}:" frontmatter key` :
|
|
263
262
|
`invalid "${key}:" frontmatter timestamp "${info.value}"`);
|
|
263
|
+
/* the state tracked while walking the Markdown tokens */
|
|
264
264
|
const artifacts = new Array();
|
|
265
265
|
const stack = new Array();
|
|
266
266
|
let current = null;
|
|
@@ -278,15 +278,16 @@ export const parseFile = (ctx, source) => {
|
|
|
278
278
|
if (token.type === "heading") {
|
|
279
279
|
flush();
|
|
280
280
|
group = null;
|
|
281
|
-
const
|
|
281
|
+
const { depth, text } = token;
|
|
282
|
+
const heading = parseHeadingText(text);
|
|
282
283
|
if (heading.malformed !== undefined)
|
|
283
284
|
ctx.diagnose(source.file, line, `malformed anchor "${heading.malformed}" in heading`);
|
|
284
|
-
if (heading.name === "" &&
|
|
285
|
+
if (heading.name === "" && depth > 1) {
|
|
285
286
|
/* a grouping container heading (e.g. "### STATES") collects
|
|
286
287
|
objects of its singular kind below the parent object */
|
|
287
|
-
const parent = stack[
|
|
288
|
+
const parent = stack[depth - 2];
|
|
288
289
|
if (parent === undefined)
|
|
289
|
-
ctx.diagnose(source.file, line, `heading level ${
|
|
290
|
+
ctx.diagnose(source.file, line, `heading level ${depth} without parent object`);
|
|
290
291
|
else {
|
|
291
292
|
group = { parent, kind: heading.kind.replace(/S$/, "") };
|
|
292
293
|
current = parent;
|
|
@@ -295,7 +296,7 @@ export const parseFile = (ctx, source) => {
|
|
|
295
296
|
else {
|
|
296
297
|
const object = {
|
|
297
298
|
kind: heading.kind,
|
|
298
|
-
id: heading.id ?? (
|
|
299
|
+
id: heading.id ?? (depth === 1 ? heading.paren : undefined) ?? slugify(heading.name),
|
|
299
300
|
name: heading.name,
|
|
300
301
|
properties: [],
|
|
301
302
|
childs: []
|
|
@@ -307,7 +308,7 @@ export const parseFile = (ctx, source) => {
|
|
|
307
308
|
if (heading.primary)
|
|
308
309
|
object.primary = true;
|
|
309
310
|
ctx.objectMeta.set(object, { file: source.file, line });
|
|
310
|
-
if (
|
|
311
|
+
if (depth === 1) {
|
|
311
312
|
artifacts.push({
|
|
312
313
|
created: created.date ?? new Date(),
|
|
313
314
|
modified: modified.date ?? new Date(),
|
|
@@ -316,16 +317,16 @@ export const parseFile = (ctx, source) => {
|
|
|
316
317
|
stack.length = 0;
|
|
317
318
|
}
|
|
318
319
|
else {
|
|
319
|
-
const parent = stack[
|
|
320
|
+
const parent = stack[depth - 2];
|
|
320
321
|
if (parent === undefined) {
|
|
321
|
-
ctx.diagnose(source.file, line, `heading level ${
|
|
322
|
+
ctx.diagnose(source.file, line, `heading level ${depth} without parent object`);
|
|
322
323
|
line += (token.raw.match(/\n/g) ?? []).length;
|
|
323
324
|
continue;
|
|
324
325
|
}
|
|
325
326
|
parent.childs.push(object);
|
|
326
327
|
}
|
|
327
|
-
stack.length =
|
|
328
|
-
stack[
|
|
328
|
+
stack.length = depth - 1;
|
|
329
|
+
stack[depth - 1] = object;
|
|
329
330
|
current = object;
|
|
330
331
|
}
|
|
331
332
|
}
|
|
@@ -119,5 +119,11 @@ export const splitItems = (text) => {
|
|
|
119
119
|
else
|
|
120
120
|
parts[parts.length - 1] += char;
|
|
121
121
|
}
|
|
122
|
-
return parts.map((part) =>
|
|
122
|
+
return parts.map((part) => {
|
|
123
|
+
/* unquote a fully double-quoted item, mirroring the member
|
|
124
|
+
quoting of the value expression lexer */
|
|
125
|
+
const item = part.trim();
|
|
126
|
+
const m = item.match(/^"((?:\\"|[^"])*)"$/);
|
|
127
|
+
return m !== null ? m[1].replace(/\\"/g, "\"") : item;
|
|
128
|
+
}).filter((part) => part !== "");
|
|
123
129
|
};
|
package/dst/specbook-parse.js
CHANGED
|
@@ -18,10 +18,14 @@ export class Parser {
|
|
|
18
18
|
/* parse all source files into the specification AST and
|
|
19
19
|
optionally validate the result against a configuration */
|
|
20
20
|
parse(sources, config) {
|
|
21
|
+
/* reset the parsing context to allow a reuse of the parser */
|
|
22
|
+
this.ctx = new ParseContext();
|
|
23
|
+
/* parse all source files into their artifacts */
|
|
21
24
|
const artifacts = new Array();
|
|
22
25
|
for (const source of sources)
|
|
23
26
|
artifacts.push(...parseFile(this.ctx, source));
|
|
24
27
|
const specification = { artifacts };
|
|
28
|
+
/* validate the resulting specification AST */
|
|
25
29
|
if (artifacts.length > 0) {
|
|
26
30
|
this.ctx.linkIndex = buildLinkIndex(specification);
|
|
27
31
|
if (config !== undefined)
|
|
@@ -9,6 +9,7 @@ export type SchemaObject = {
|
|
|
9
9
|
desc?: string;
|
|
10
10
|
optional?: boolean;
|
|
11
11
|
diagram?: SchemaDiagram;
|
|
12
|
+
format?: SchemaFormat;
|
|
12
13
|
props?: SchemaProperty[];
|
|
13
14
|
childs?: SchemaObject[];
|
|
14
15
|
};
|
|
@@ -24,8 +25,14 @@ export type SchemaDiagram = {
|
|
|
24
25
|
onlyConnected?: boolean;
|
|
25
26
|
collapse?: boolean;
|
|
26
27
|
qualified?: boolean;
|
|
28
|
+
properties?: string[];
|
|
27
29
|
config?: Partial<GradiaConfig>;
|
|
28
30
|
};
|
|
31
|
+
export type SchemaFormat = {
|
|
32
|
+
type?: "auto" | "complex" | "concise";
|
|
33
|
+
maxTableColumns?: number;
|
|
34
|
+
withUnusedProps?: boolean;
|
|
35
|
+
};
|
|
29
36
|
export type SchemaProperty = {
|
|
30
37
|
name: string;
|
|
31
38
|
desc?: string;
|
|
@@ -13,13 +13,13 @@ const GradiaConfigSchemas = Object.fromEntries(Object.entries(Gradia.config).map
|
|
|
13
13
|
typeof value === "boolean" ? v.boolean() : typeof value === "number" ? v.number() : v.string()]));
|
|
14
14
|
const SchemaDiagramConfig = v.partial(v.strictObject(GradiaConfigSchemas, (issue) => issue.expected === "never" ? "unknown Gradia rendering option" :
|
|
15
15
|
"expected a map of Gradia rendering options"));
|
|
16
|
-
const SchemaProperty = v.
|
|
16
|
+
const SchemaProperty = v.strictObject({
|
|
17
17
|
name: v.string(),
|
|
18
18
|
desc: v.optional(v.string()),
|
|
19
19
|
value: v.optional(v.string()),
|
|
20
20
|
optional: v.optional(v.boolean())
|
|
21
21
|
});
|
|
22
|
-
const SchemaDiagram = v.
|
|
22
|
+
const SchemaDiagram = v.strictObject({
|
|
23
23
|
type: v.optional(v.picklist(["graph", "hub", "grid"])),
|
|
24
24
|
nodes: v.optional(v.string()),
|
|
25
25
|
edges: v.optional(v.string()),
|
|
@@ -31,9 +31,15 @@ const SchemaDiagram = v.object({
|
|
|
31
31
|
onlyConnected: v.optional(v.boolean()),
|
|
32
32
|
collapse: v.optional(v.boolean()),
|
|
33
33
|
qualified: v.optional(v.boolean()),
|
|
34
|
+
properties: v.optional(v.array(v.string())),
|
|
34
35
|
config: v.optional(SchemaDiagramConfig)
|
|
35
36
|
});
|
|
36
|
-
const
|
|
37
|
+
const SchemaFormat = v.strictObject({
|
|
38
|
+
type: v.optional(v.picklist(["auto", "complex", "concise"])),
|
|
39
|
+
maxTableColumns: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1))),
|
|
40
|
+
withUnusedProps: v.optional(v.boolean())
|
|
41
|
+
});
|
|
42
|
+
const SchemaObject = v.strictObject({
|
|
37
43
|
kind: v.string(),
|
|
38
44
|
name: v.optional(v.string()),
|
|
39
45
|
id: v.optional(v.string()),
|
|
@@ -41,6 +47,7 @@ const SchemaObject = v.object({
|
|
|
41
47
|
desc: v.optional(v.string()),
|
|
42
48
|
optional: v.optional(v.boolean()),
|
|
43
49
|
diagram: v.optional(SchemaDiagram),
|
|
50
|
+
format: v.optional(SchemaFormat),
|
|
44
51
|
props: v.optional(v.array(SchemaProperty)),
|
|
45
52
|
childs: v.optional(v.array(v.lazy(() => SchemaObject)))
|
|
46
53
|
});
|
package/dst/specbook-theme.js
CHANGED
|
@@ -26,7 +26,7 @@ export const themeStylesheet = (colors) => ":root {\n" +
|
|
|
26
26
|
aligned with the layer-2 CSS mapping of the HTML rendering */
|
|
27
27
|
export const themeMapping = (colors, style) => {
|
|
28
28
|
const idx = style === "dark" ?
|
|
29
|
-
{ text:
|
|
29
|
+
{ text: 29, muted: 22, symbol: 20, border: 16, accent: 24 } :
|
|
30
30
|
{ text: 5, muted: 22, symbol: 24, border: 28, accent: 12 };
|
|
31
31
|
return {
|
|
32
32
|
text: colors.base[idx.text - 1],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rse/specbook",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
4
4
|
"publishConfig": { "access": "public" },
|
|
5
5
|
"description": "Markdown-based Specification Format (API, CLI and MCP service)",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"json5": "2.2.3",
|
|
24
24
|
"pdf-lib": "1.17.1",
|
|
25
25
|
"subset-font": "2.5.0",
|
|
26
|
-
"@rse/mrcs": "0.9.
|
|
27
|
-
"@rse/gradia": "0.
|
|
26
|
+
"@rse/mrcs": "0.9.2",
|
|
27
|
+
"@rse/gradia": "1.0.0",
|
|
28
28
|
"@toon-format/toon": "4.1.1",
|
|
29
29
|
"valibot": "1.4.2",
|
|
30
30
|
"nunjucks": "3.2.4",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@swc/html": "1.16.1",
|
|
34
34
|
"tokenizr": "2.2.1",
|
|
35
35
|
"playwright": "1.62.1",
|
|
36
|
-
"ai": "7.0.
|
|
36
|
+
"ai": "7.0.69",
|
|
37
37
|
"@ai-sdk/anthropic": "4.0.39",
|
|
38
38
|
"@ai-sdk/openai": "4.0.43",
|
|
39
39
|
"@openrouter/ai-sdk-provider": "3.0.0",
|