md-verified 0.1.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/LICENSE +21 -0
- package/README.md +493 -0
- package/dist/check.d.ts +2 -0
- package/dist/check.js +342 -0
- package/dist/src/assertions.d.ts +25 -0
- package/dist/src/assertions.js +84 -0
- package/dist/src/coerce.d.ts +19 -0
- package/dist/src/coerce.js +122 -0
- package/dist/src/covers.d.ts +38 -0
- package/dist/src/covers.js +59 -0
- package/dist/src/framework.d.ts +64 -0
- package/dist/src/framework.js +73 -0
- package/dist/src/index.d.ts +19 -0
- package/dist/src/index.js +13 -0
- package/dist/src/mdast-gfm.d.ts +22 -0
- package/dist/src/mdast-gfm.js +90 -0
- package/dist/src/mermaid.d.ts +17 -0
- package/dist/src/mermaid.js +304 -0
- package/dist/src/parser.d.ts +26 -0
- package/dist/src/parser.js +423 -0
- package/dist/src/references.d.ts +33 -0
- package/dist/src/references.js +198 -0
- package/dist/src/report.d.ts +52 -0
- package/dist/src/report.js +278 -0
- package/dist/src/reviews.d.ts +19 -0
- package/dist/src/reviews.js +137 -0
- package/dist/src/runner.d.ts +94 -0
- package/dist/src/runner.js +353 -0
- package/dist/src/symbols.d.ts +15 -0
- package/dist/src/symbols.js +112 -0
- package/dist/src/types.d.ts +278 -0
- package/dist/src/types.js +27 -0
- package/package.json +70 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/** Shared vocabulary for the whole framework. */
|
|
2
|
+
import type { Root } from 'mdast';
|
|
3
|
+
/** The three kinds of native Markdown asset an anchor can bind to. */
|
|
4
|
+
export type AnchorKind = 'table' | 'mermaid' | 'list';
|
|
5
|
+
/** Lifecycle of a single anchor or case. */
|
|
6
|
+
export type Status = 'pending' | 'passed' | 'failed' | 'skipped';
|
|
7
|
+
/** The glyph that leads an anchor's blockquote, per status. */
|
|
8
|
+
export declare const STATUS_GLYPH: Record<Status, string>;
|
|
9
|
+
/** An unstamped review, analogous to the pending hammer on an anchor. */
|
|
10
|
+
export declare const REVIEW_PENDING_GLYPH = "\uD83D\uDC41\uFE0F";
|
|
11
|
+
/** Every glyph we recognise as "this is a status marker", incl. bare variants. */
|
|
12
|
+
export declare const KNOWN_GLYPHS: Set<string>;
|
|
13
|
+
/** Human-facing parenthetical appended after the anchor id. */
|
|
14
|
+
export declare const STATUS_SUFFIX: Record<Status, string>;
|
|
15
|
+
/** A source position, 1-based lines/columns as mdast reports them. */
|
|
16
|
+
export interface Point {
|
|
17
|
+
line: number;
|
|
18
|
+
column: number;
|
|
19
|
+
offset: number;
|
|
20
|
+
}
|
|
21
|
+
/** A single parsed table row, handed to `verify.table` callbacks. */
|
|
22
|
+
export interface TableRow {
|
|
23
|
+
/** Coerced value by header name *and* by schema field name. */
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
/** Zero-based index among data rows. */
|
|
26
|
+
readonly $index: number;
|
|
27
|
+
/** 1-based line in the source file. */
|
|
28
|
+
readonly $line: number;
|
|
29
|
+
/** Untouched cell text, keyed by header. */
|
|
30
|
+
readonly $raw: Readonly<Record<string, string>>;
|
|
31
|
+
/** Untouched cell text, in column order. */
|
|
32
|
+
readonly $cells: readonly string[];
|
|
33
|
+
/** Header labels, in column order. */
|
|
34
|
+
readonly $headers: readonly string[];
|
|
35
|
+
}
|
|
36
|
+
/** A row that could not be built, e.g. a cell that failed to coerce. */
|
|
37
|
+
export interface RowDefect {
|
|
38
|
+
/** Zero-based index the row would have had among data rows. */
|
|
39
|
+
index: number;
|
|
40
|
+
/** 1-based line in the source file. */
|
|
41
|
+
line: number;
|
|
42
|
+
message: string;
|
|
43
|
+
}
|
|
44
|
+
/** A whole parsed table. */
|
|
45
|
+
export interface ParsedTable {
|
|
46
|
+
headers: string[];
|
|
47
|
+
/** Column alignment as declared in the delimiter row. */
|
|
48
|
+
align: Array<'left' | 'right' | 'center' | null>;
|
|
49
|
+
/** Rows that were built successfully. Defective rows are absent. */
|
|
50
|
+
rows: TableRow[];
|
|
51
|
+
/**
|
|
52
|
+
* Rows that could not be built. These are reported as failing cases rather
|
|
53
|
+
* than handed to a handler, so a bad cell never reaches glue code.
|
|
54
|
+
*/
|
|
55
|
+
defects: RowDefect[];
|
|
56
|
+
/** Declared schema, if the anchor carried a `**Schema:**` line. */
|
|
57
|
+
schema: SchemaField[] | null;
|
|
58
|
+
}
|
|
59
|
+
/** One `name: Type` pair from a `**Schema:**` declaration. */
|
|
60
|
+
export interface SchemaField {
|
|
61
|
+
name: string;
|
|
62
|
+
type: string;
|
|
63
|
+
/** Trailing `?` marks the column as optional / nullable. */
|
|
64
|
+
optional: boolean;
|
|
65
|
+
}
|
|
66
|
+
/** A bullet or ordered list item. */
|
|
67
|
+
export interface ListItem {
|
|
68
|
+
/** Item text with Markdown inline syntax preserved. */
|
|
69
|
+
text: string;
|
|
70
|
+
/** `true` / `false` for `- [x]` / `- [ ]` items, `null` otherwise. */
|
|
71
|
+
checked: boolean | null;
|
|
72
|
+
/** Nesting depth, 0 at the top level. */
|
|
73
|
+
depth: number;
|
|
74
|
+
/** Zero-based index among siblings. */
|
|
75
|
+
index: number;
|
|
76
|
+
/** 1-based line in the source file. */
|
|
77
|
+
line: number;
|
|
78
|
+
children: ListItem[];
|
|
79
|
+
}
|
|
80
|
+
/** A whole parsed list. */
|
|
81
|
+
export interface ParsedList {
|
|
82
|
+
ordered: boolean;
|
|
83
|
+
/** Top-level items; nested items hang off `children`. */
|
|
84
|
+
items: ListItem[];
|
|
85
|
+
/** Every item at every depth, in document order. */
|
|
86
|
+
flat: ListItem[];
|
|
87
|
+
}
|
|
88
|
+
/** Metadata lines parsed out of the anchor blockquote, e.g. `**Schema:**`. */
|
|
89
|
+
export type AnchorMeta = Record<string, string>;
|
|
90
|
+
/** An anchor: the blockquote plus the asset it points at. */
|
|
91
|
+
export interface Anchor {
|
|
92
|
+
/** The `id` from the backticks, e.g. `validateOrder`. */
|
|
93
|
+
id: string;
|
|
94
|
+
/** Resolved kind, from the label and confirmed by the target node. */
|
|
95
|
+
kind: AnchorKind;
|
|
96
|
+
/** The human-facing label as written, e.g. `Data`, `Flow`. */
|
|
97
|
+
label: string;
|
|
98
|
+
/** Status glyph found in the source. */
|
|
99
|
+
status: Status;
|
|
100
|
+
meta: AnchorMeta;
|
|
101
|
+
/**
|
|
102
|
+
* An anchor-level problem that makes the asset unusable -- a malformed
|
|
103
|
+
* schema, an unparseable diagram. The anchor is still returned so the runner
|
|
104
|
+
* can fail it and write the reason back into the document; `data` is an
|
|
105
|
+
* empty placeholder and is never handed to a handler.
|
|
106
|
+
*/
|
|
107
|
+
defect: string | null;
|
|
108
|
+
/** Parsed payload: shape depends on `kind`. */
|
|
109
|
+
data: ParsedTable | MermaidGraph | ParsedList;
|
|
110
|
+
/** 1-based line of the blockquote's first line. */
|
|
111
|
+
line: number;
|
|
112
|
+
/** Byte range of the blockquote itself. */
|
|
113
|
+
quoteRange: {
|
|
114
|
+
start: number;
|
|
115
|
+
end: number;
|
|
116
|
+
};
|
|
117
|
+
/** Byte range of the asset the anchor binds to. */
|
|
118
|
+
targetRange: {
|
|
119
|
+
start: number;
|
|
120
|
+
end: number;
|
|
121
|
+
};
|
|
122
|
+
/** Byte range of everything between them (comments live here). */
|
|
123
|
+
gapRange: {
|
|
124
|
+
start: number;
|
|
125
|
+
end: number;
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* A claim that a human has read this section against the code behind it.
|
|
130
|
+
*
|
|
131
|
+
* Prose and rationale cannot be executed. What *can* be checked is whether
|
|
132
|
+
* anyone has looked at them since the code they describe last changed -- an
|
|
133
|
+
* attestation rather than a proof, which is the honest thing to offer for the
|
|
134
|
+
* parts of a document that matter most and verify least.
|
|
135
|
+
*/
|
|
136
|
+
export interface Review {
|
|
137
|
+
id: string;
|
|
138
|
+
status: Status;
|
|
139
|
+
/** Paths, relative to the document, optionally `#symbol`-qualified. */
|
|
140
|
+
covers: string[];
|
|
141
|
+
/** The digest recorded at the last review, if any. */
|
|
142
|
+
digest: string | null;
|
|
143
|
+
/** A malformed review, e.g. one that declares nothing to cover. */
|
|
144
|
+
defect: string | null;
|
|
145
|
+
meta: AnchorMeta;
|
|
146
|
+
line: number;
|
|
147
|
+
quoteRange: {
|
|
148
|
+
start: number;
|
|
149
|
+
end: number;
|
|
150
|
+
};
|
|
151
|
+
gapRange: {
|
|
152
|
+
start: number;
|
|
153
|
+
end: number;
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/** The outcome of checking one review. */
|
|
157
|
+
export interface ReviewResult {
|
|
158
|
+
id: string;
|
|
159
|
+
line: number;
|
|
160
|
+
status: Exclude<Status, 'pending'>;
|
|
161
|
+
reason: string | null;
|
|
162
|
+
/** The digest as it is now. Written back by `--stamp`. */
|
|
163
|
+
digest: string | null;
|
|
164
|
+
/** Whether the recorded digest matched. */
|
|
165
|
+
current: boolean;
|
|
166
|
+
}
|
|
167
|
+
/** A structural problem found while parsing -- reported like a failure. */
|
|
168
|
+
export interface ParseProblem {
|
|
169
|
+
id: string | null;
|
|
170
|
+
line: number;
|
|
171
|
+
/** 1-based column, when the diagnostic points at something inline. */
|
|
172
|
+
column?: number;
|
|
173
|
+
message: string;
|
|
174
|
+
}
|
|
175
|
+
export interface ParseResult {
|
|
176
|
+
file: string;
|
|
177
|
+
source: string;
|
|
178
|
+
/** The mdast tree, kept so passes like reference checking can reuse it. */
|
|
179
|
+
tree: Root;
|
|
180
|
+
anchors: Anchor[];
|
|
181
|
+
reviews: Review[];
|
|
182
|
+
problems: ParseProblem[];
|
|
183
|
+
}
|
|
184
|
+
export type MermaidShape = 'rect' | 'round' | 'stadium' | 'subroutine' | 'cylinder' | 'circle' | 'doublecircle' | 'diamond' | 'hexagon' | 'parallelogram' | 'parallelogram-alt' | 'trapezoid' | 'trapezoid-alt' | 'asymmetric';
|
|
185
|
+
export interface MermaidNode {
|
|
186
|
+
id: string;
|
|
187
|
+
/** Display label; falls back to the id when the node is bare. */
|
|
188
|
+
label: string;
|
|
189
|
+
shape: MermaidShape;
|
|
190
|
+
/** Id of the enclosing `subgraph`, when there is one. */
|
|
191
|
+
subgraph: string | null;
|
|
192
|
+
}
|
|
193
|
+
export type EdgeStyle = 'normal' | 'thick' | 'dotted' | 'invisible';
|
|
194
|
+
export interface MermaidEdge {
|
|
195
|
+
from: string;
|
|
196
|
+
to: string;
|
|
197
|
+
/** Edge label, from `-->|text|` or `-- text -->`. */
|
|
198
|
+
label: string | null;
|
|
199
|
+
style: EdgeStyle;
|
|
200
|
+
/** `false` for open links like `A --- B`. */
|
|
201
|
+
directed: boolean;
|
|
202
|
+
/** The link as written, e.g. `-->`. */
|
|
203
|
+
raw: string;
|
|
204
|
+
}
|
|
205
|
+
export interface MermaidSubgraph {
|
|
206
|
+
id: string;
|
|
207
|
+
label: string;
|
|
208
|
+
nodes: string[];
|
|
209
|
+
}
|
|
210
|
+
export interface MermaidGraph {
|
|
211
|
+
/** `graph`, `flowchart`, or whatever the header declared. */
|
|
212
|
+
type: string;
|
|
213
|
+
/** `TD`, `LR`, ... Defaults to `TB` when the header omits it. */
|
|
214
|
+
direction: string;
|
|
215
|
+
nodes: MermaidNode[];
|
|
216
|
+
edges: MermaidEdge[];
|
|
217
|
+
subgraphs: MermaidSubgraph[];
|
|
218
|
+
/** The original diagram source. */
|
|
219
|
+
raw: string;
|
|
220
|
+
/** Look a node up by id. */
|
|
221
|
+
node(id: string): MermaidNode | undefined;
|
|
222
|
+
/** Edges leaving `id`. */
|
|
223
|
+
from(id: string): MermaidEdge[];
|
|
224
|
+
/** Edges entering `id`. */
|
|
225
|
+
to(id: string): MermaidEdge[];
|
|
226
|
+
/** Is there a direct edge `a -> b`? */
|
|
227
|
+
hasEdge(a: string, b: string): boolean;
|
|
228
|
+
/** Is `b` reachable from `a` by following directed edges? */
|
|
229
|
+
hasPath(a: string, b: string): boolean;
|
|
230
|
+
/** Nodes with no incoming edges. */
|
|
231
|
+
roots(): MermaidNode[];
|
|
232
|
+
/** Nodes with no outgoing edges. */
|
|
233
|
+
leaves(): MermaidNode[];
|
|
234
|
+
}
|
|
235
|
+
/** One executed case: a row, an edge, an item, or a whole asset. */
|
|
236
|
+
export interface CaseResult {
|
|
237
|
+
/** Short human label, e.g. `row 2` or `Cart -> Payment`. */
|
|
238
|
+
name: string;
|
|
239
|
+
status: Exclude<Status, 'pending'>;
|
|
240
|
+
/** Failure message, or `null` when the case passed. */
|
|
241
|
+
error: string | null;
|
|
242
|
+
stack: string | null;
|
|
243
|
+
durationMs: number;
|
|
244
|
+
/** 1-based source line the case came from, when known. */
|
|
245
|
+
line: number | null;
|
|
246
|
+
}
|
|
247
|
+
/** Everything that happened for a single anchor. */
|
|
248
|
+
export interface AnchorResult {
|
|
249
|
+
id: string;
|
|
250
|
+
kind: AnchorKind;
|
|
251
|
+
label: string;
|
|
252
|
+
line: number;
|
|
253
|
+
status: Exclude<Status, 'pending'>;
|
|
254
|
+
/** Why an anchor was skipped, or how it failed structurally. */
|
|
255
|
+
reason: string | null;
|
|
256
|
+
cases: CaseResult[];
|
|
257
|
+
}
|
|
258
|
+
export interface RunSummary {
|
|
259
|
+
anchors: number;
|
|
260
|
+
reviews: number;
|
|
261
|
+
reviewsStale: number;
|
|
262
|
+
passed: number;
|
|
263
|
+
failed: number;
|
|
264
|
+
skipped: number;
|
|
265
|
+
cases: number;
|
|
266
|
+
casesPassed: number;
|
|
267
|
+
casesFailed: number;
|
|
268
|
+
durationMs: number;
|
|
269
|
+
}
|
|
270
|
+
export interface RunResult {
|
|
271
|
+
file: string;
|
|
272
|
+
ok: boolean;
|
|
273
|
+
source: string;
|
|
274
|
+
anchors: AnchorResult[];
|
|
275
|
+
reviews: ReviewResult[];
|
|
276
|
+
problems: ParseProblem[];
|
|
277
|
+
summary: RunSummary;
|
|
278
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** The glyph that leads an anchor's blockquote, per status. */
|
|
2
|
+
export const STATUS_GLYPH = {
|
|
3
|
+
pending: '\u{1F6E0}️', // hammer and wrench
|
|
4
|
+
passed: '✅', // white heavy check mark
|
|
5
|
+
failed: '❌', // cross mark
|
|
6
|
+
skipped: '⚠️', // warning sign
|
|
7
|
+
};
|
|
8
|
+
/** An unstamped review, analogous to the pending hammer on an anchor. */
|
|
9
|
+
export const REVIEW_PENDING_GLYPH = '\u{1F441}\uFE0F';
|
|
10
|
+
/** Every glyph we recognise as "this is a status marker", incl. bare variants. */
|
|
11
|
+
export const KNOWN_GLYPHS = new Set([
|
|
12
|
+
'\u{1F6E0}️',
|
|
13
|
+
'\u{1F6E0}',
|
|
14
|
+
'✅',
|
|
15
|
+
'❌',
|
|
16
|
+
'⚠️',
|
|
17
|
+
'⚠',
|
|
18
|
+
'\u{1F441}\uFE0F',
|
|
19
|
+
'\u{1F441}',
|
|
20
|
+
]);
|
|
21
|
+
/** Human-facing parenthetical appended after the anchor id. */
|
|
22
|
+
export const STATUS_SUFFIX = {
|
|
23
|
+
pending: '',
|
|
24
|
+
passed: '',
|
|
25
|
+
failed: '(Failed)',
|
|
26
|
+
skipped: '(Skipped)',
|
|
27
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "md-verified",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Executable specifications from beautiful, native Markdown.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Kristian Dupont",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/kristiandupont/md-verified.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/kristiandupont/md-verified#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/kristiandupont/md-verified/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"markdown",
|
|
17
|
+
"documentation",
|
|
18
|
+
"specification",
|
|
19
|
+
"testing",
|
|
20
|
+
"bdd",
|
|
21
|
+
"bun",
|
|
22
|
+
"mermaid"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"bin": {
|
|
26
|
+
"md-verified": "./dist/check.js"
|
|
27
|
+
},
|
|
28
|
+
"main": "./dist/src/index.js",
|
|
29
|
+
"module": "./dist/src/index.js",
|
|
30
|
+
"types": "./dist/src/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/src/index.d.ts",
|
|
34
|
+
"default": "./dist/src/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"README.md",
|
|
41
|
+
"LICENSE"
|
|
42
|
+
],
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=24",
|
|
45
|
+
"bun": ">=1.2.0"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"check": "bun run check.ts",
|
|
49
|
+
"test": "bun test",
|
|
50
|
+
"demo": "bun run check.ts examples/spec.md",
|
|
51
|
+
"docs": "bun run check.ts README.md 'docs/*.md' examples/spec.md",
|
|
52
|
+
"demo:fail": "bun run check.ts examples/broken.md --report",
|
|
53
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
54
|
+
"prepublishOnly": "bun run build && bun test && ./scripts/smoke.sh node",
|
|
55
|
+
"typecheck": "tsc --noEmit",
|
|
56
|
+
"ci": "bun run typecheck && bun test && bun run build && bun run docs",
|
|
57
|
+
"smoke": "./scripts/smoke.sh"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"mdast-util-from-markdown": "^2.0.3",
|
|
61
|
+
"micromark-extension-gfm-table": "^2.1.1",
|
|
62
|
+
"micromark-extension-gfm-task-list-item": "^2.1.0",
|
|
63
|
+
"typescript": "^5.9.2"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@types/bun": "latest",
|
|
67
|
+
"@types/mdast": "^4.0.4",
|
|
68
|
+
"@types/unist": "^3.0.3"
|
|
69
|
+
}
|
|
70
|
+
}
|