check-rule-mate 0.5.1 → 0.5.2
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 +14 -0
- package/bin/generate-docs.js +434 -0
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -31,6 +31,7 @@ Use **check-rule-mate** if you:
|
|
|
31
31
|
- Need async validations (API calls, database checks, etc.)
|
|
32
32
|
- Prefer rule-driven validation instead of tightly coupled schemas
|
|
33
33
|
- Want clean separation between rules, data, and messages
|
|
34
|
+
- You want to have auto documentation of your forms
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
## Core Concepts
|
|
@@ -55,6 +56,7 @@ This separation makes the system flexible, scalable, and easy to maintain.
|
|
|
55
56
|
- Strict or loose schema matching
|
|
56
57
|
- i18n-ready error messages
|
|
57
58
|
- Framework-agnostic (frontend or backend)
|
|
59
|
+
- Auto documentation (automatic generated by a CLI command)
|
|
58
60
|
|
|
59
61
|
## Table of Contents
|
|
60
62
|
|
|
@@ -64,6 +66,7 @@ This separation makes the system flexible, scalable, and easy to maintain.
|
|
|
64
66
|
- [Running Tests](#Repository---Running-Tests)
|
|
65
67
|
- [How It Works](#How-It-Works)
|
|
66
68
|
- [Basic Usage](#Basic-Usage)
|
|
69
|
+
- [Auto documentation](#Auto-Documentation)
|
|
67
70
|
- [Defining Validation](#Defining-Validation)
|
|
68
71
|
- [1. Schema](#Defining-a-Schema-What-to-validate)
|
|
69
72
|
- [2. Rules](#Defining-Rules-How-to-validate)
|
|
@@ -164,6 +167,17 @@ When is **invalid** and **has errors**:
|
|
|
164
167
|
}
|
|
165
168
|
```
|
|
166
169
|
|
|
170
|
+
### Auto Documentation
|
|
171
|
+
|
|
172
|
+
check-rule-mate contains a script to generate documentation based in your rules, schemas and error messages.
|
|
173
|
+
|
|
174
|
+
To use that it is simple, you only need to run this command:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
npx generate-docs --rules rules-path --schemas schemas-path --errors errors-path --out file.html
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
This will generate a HTML file containing the rules, schemas and errors.
|
|
167
181
|
|
|
168
182
|
## Defining Validation
|
|
169
183
|
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* check-rule-mate — Auto Documentation Generator
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// import fs from "fs";
|
|
7
|
+
// import path from "path";
|
|
8
|
+
// import process from "process";
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const process = require('process')
|
|
13
|
+
|
|
14
|
+
/* ---------------------------------------
|
|
15
|
+
* CLI ARGS
|
|
16
|
+
* -------------------------------------*/
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
const getArg = (flag) => {
|
|
19
|
+
const index = args.indexOf(flag);
|
|
20
|
+
return index !== -1 ? args[index + 1] : null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const RULES_DIR = getArg('--rules');
|
|
24
|
+
const SCHEMAS_DIR = getArg('--schemas');
|
|
25
|
+
const ERRORS_DIR = getArg('--errors')
|
|
26
|
+
const OUTPUT = getArg('--out') || "check-rule-mate-docs.html";
|
|
27
|
+
|
|
28
|
+
if (!RULES_DIR || !SCHEMAS_DIR) {
|
|
29
|
+
console.error(`
|
|
30
|
+
Usage:
|
|
31
|
+
node generate-docs.js --rules ./rules --schemas ./schemas --out docs.html
|
|
32
|
+
`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* ---------------------------------------
|
|
37
|
+
* HELPERS
|
|
38
|
+
* -------------------------------------*/
|
|
39
|
+
const readJSONFiles = (dir) => {
|
|
40
|
+
return fs.readdirSync(dir)
|
|
41
|
+
.filter(f => f.endsWith(".json"))
|
|
42
|
+
.map(file => ({
|
|
43
|
+
name: file.replace(".json", ""),
|
|
44
|
+
content: JSON.parse(fs.readFileSync(path.join(dir, file), "utf-8"))
|
|
45
|
+
}));
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const rulesFiles = readJSONFiles(RULES_DIR);
|
|
49
|
+
const schemasFiles = readJSONFiles(SCHEMAS_DIR);
|
|
50
|
+
const errorsFiles = readJSONFiles(ERRORS_DIR);
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
/* ---------------------------------------
|
|
54
|
+
* INDEX RELATIONSHIPS
|
|
55
|
+
* -------------------------------------*/
|
|
56
|
+
const ruleUsageMap = {};
|
|
57
|
+
const errorUsageMap = {}
|
|
58
|
+
|
|
59
|
+
schemasFiles.forEach(schemaFile => {
|
|
60
|
+
Object.entries(schemaFile.content).forEach(([field, config]) => {
|
|
61
|
+
const ruleName = config.rule.split("--")[0];
|
|
62
|
+
if (!ruleUsageMap[ruleName]) ruleUsageMap[ruleName] = [];
|
|
63
|
+
ruleUsageMap[ruleName].push({
|
|
64
|
+
schema: schemaFile.name,
|
|
65
|
+
field,
|
|
66
|
+
rule: config.rule
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
rulesFiles.forEach(rulesFile => {
|
|
72
|
+
Object.entries(rulesFile.content).forEach(([field, config]) => {
|
|
73
|
+
Object.entries(config.error).forEach(([key, value]) => {
|
|
74
|
+
const errorName = value;
|
|
75
|
+
if (!errorUsageMap[errorName]) errorUsageMap[errorName] = [];
|
|
76
|
+
errorUsageMap[errorName].push({
|
|
77
|
+
file: rulesFile.name,
|
|
78
|
+
field,
|
|
79
|
+
error: value
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
if (config?.modifier) {
|
|
83
|
+
Object.entries(config.modifier).forEach(([modifierKey, modifier]) => {
|
|
84
|
+
if (modifier?.error) {
|
|
85
|
+
Object.entries(modifier.error).forEach(([key, value]) => {
|
|
86
|
+
const errorName = value;
|
|
87
|
+
if (!errorUsageMap[errorName]) errorUsageMap[errorName] = [];
|
|
88
|
+
errorUsageMap[errorName].push({
|
|
89
|
+
file: rulesFile.name,
|
|
90
|
+
field,
|
|
91
|
+
error: value
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
/* ---------------------------------------
|
|
102
|
+
* HTML GENERATION
|
|
103
|
+
* -------------------------------------*/
|
|
104
|
+
const html = `
|
|
105
|
+
<!DOCTYPE html>
|
|
106
|
+
<html lang="en">
|
|
107
|
+
<head>
|
|
108
|
+
<meta charset="UTF-8" />
|
|
109
|
+
<title>check-rule-mate — Documentation</title>
|
|
110
|
+
<style>
|
|
111
|
+
${generateCSS()}
|
|
112
|
+
</style>
|
|
113
|
+
</head>
|
|
114
|
+
<body>
|
|
115
|
+
|
|
116
|
+
<aside class="sidebar">
|
|
117
|
+
<input id="search" placeholder="Search..." />
|
|
118
|
+
|
|
119
|
+
<h3>Schemas</h3>
|
|
120
|
+
${schemasFiles.map(s => `
|
|
121
|
+
<a href="#schema-${s.name}">${s.name}</a>
|
|
122
|
+
`).join("")}
|
|
123
|
+
|
|
124
|
+
<h3>Rules</h3>
|
|
125
|
+
${rulesFiles.map(rf =>
|
|
126
|
+
Object.keys(rf.content).map(rule => `
|
|
127
|
+
<a href="#rule-${rule}">${rf.name} - ${rule}</a>
|
|
128
|
+
`).join("")
|
|
129
|
+
).join("")}
|
|
130
|
+
|
|
131
|
+
<h3>Errors</h3>
|
|
132
|
+
${errorsFiles.map(errorFile =>
|
|
133
|
+
Object.keys(errorFile.content).map(error => `
|
|
134
|
+
<a href="#error-${error}">${errorFile.name} - ${error}</a>
|
|
135
|
+
`).join("")
|
|
136
|
+
).join("")}
|
|
137
|
+
</aside>
|
|
138
|
+
|
|
139
|
+
<main>
|
|
140
|
+
<section>
|
|
141
|
+
<h1>check-rule-mate</h1>
|
|
142
|
+
<p class="muted">
|
|
143
|
+
Visual documentation of validation rules and schemas.
|
|
144
|
+
</p>
|
|
145
|
+
</section>
|
|
146
|
+
|
|
147
|
+
${renderSchemas(schemasFiles)}
|
|
148
|
+
${renderRules(rulesFiles, ruleUsageMap)}
|
|
149
|
+
${renderErrors(errorsFiles, errorUsageMap)}
|
|
150
|
+
</main>
|
|
151
|
+
|
|
152
|
+
<script>
|
|
153
|
+
${generateClientJS()}
|
|
154
|
+
</script>
|
|
155
|
+
|
|
156
|
+
</body>
|
|
157
|
+
</html>
|
|
158
|
+
`;
|
|
159
|
+
|
|
160
|
+
fs.writeFileSync(OUTPUT, html);
|
|
161
|
+
console.log(`✔ Documentation generated at ${OUTPUT}`);
|
|
162
|
+
|
|
163
|
+
/* ---------------------------------------
|
|
164
|
+
* RENDERERS
|
|
165
|
+
* -------------------------------------*/
|
|
166
|
+
function renderSchemas(schemas) {
|
|
167
|
+
return schemas.map(schema => `
|
|
168
|
+
<section id="schema-${schema.name}" class="card">
|
|
169
|
+
<h2>Schema: ${schema.name}</h2>
|
|
170
|
+
|
|
171
|
+
<table>
|
|
172
|
+
<thead>
|
|
173
|
+
<tr>
|
|
174
|
+
<th>Field</th>
|
|
175
|
+
<th>Rule</th>
|
|
176
|
+
<th class="text-center">Required</th>
|
|
177
|
+
<th class="text-center">Cache</th>
|
|
178
|
+
</tr>
|
|
179
|
+
</thead>
|
|
180
|
+
<tbody>
|
|
181
|
+
${Object.entries(schema.content).map(([field, cfg]) => `
|
|
182
|
+
<tr>
|
|
183
|
+
<td>${field}</td>
|
|
184
|
+
<td>
|
|
185
|
+
<a href="#rule-${cfg.rule.split("--")[0]}">
|
|
186
|
+
${cfg.rule}
|
|
187
|
+
</a>
|
|
188
|
+
</td>
|
|
189
|
+
<td class="text-center">${cfg.required ? "✔" : "optional"}</td>
|
|
190
|
+
<td class="text-center">${cfg.cache === false ? "off" : "✔"}</td>
|
|
191
|
+
</tr>
|
|
192
|
+
`).join("")}
|
|
193
|
+
</tbody>
|
|
194
|
+
</table>
|
|
195
|
+
</section>
|
|
196
|
+
`).join("");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function renderRules(rulesFiles, usageMap) {
|
|
200
|
+
return rulesFiles.map(file =>
|
|
201
|
+
Object.entries(file.content).map(([ruleName, rule]) => `
|
|
202
|
+
<section id="rule-${ruleName}" class="card">
|
|
203
|
+
<h2>Rule: ${ruleName} (${file.name})</h2>
|
|
204
|
+
${rule?.docs?.description ?
|
|
205
|
+
`<p>${rule.docs.description}</p>`
|
|
206
|
+
: ''}
|
|
207
|
+
|
|
208
|
+
<h3>Validation Flow</h3>
|
|
209
|
+
<div class="flow">
|
|
210
|
+
${rule.validate.map(v => `
|
|
211
|
+
<div class="flow-step">${v}</div>
|
|
212
|
+
<div class="flow-arrow">→</div>
|
|
213
|
+
`).join("")}
|
|
214
|
+
<div class="flow-step success">valid</div>
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
<h3>Error Codes</h3>
|
|
218
|
+
${renderRulesErrors(rule.error)}
|
|
219
|
+
|
|
220
|
+
${rule.modifier ? `
|
|
221
|
+
<h3>Modifiers</h3>
|
|
222
|
+
${Object.entries(rule.modifier).map(([mod, modRule]) => `
|
|
223
|
+
<div class="modifier">
|
|
224
|
+
<span class="tag modifier">${mod}</span>
|
|
225
|
+
${modRule?.docs?.description ? `<p>${modRule.docs.description}</p>` : ''}
|
|
226
|
+
|
|
227
|
+
<div class="flow">
|
|
228
|
+
${modRule.validate.map(v => `
|
|
229
|
+
<div class="flow-step">${v}</div>
|
|
230
|
+
<div class="flow-arrow">→</div>
|
|
231
|
+
`).join("")}
|
|
232
|
+
<div class="flow-step success">valid</div>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
<h4>Error Codes</h4>
|
|
236
|
+
${renderRulesErrors(modRule.error)}
|
|
237
|
+
</div>
|
|
238
|
+
`).join("")}
|
|
239
|
+
` : ""}
|
|
240
|
+
|
|
241
|
+
<h3>Used by Schemas</h3>
|
|
242
|
+
<ul>
|
|
243
|
+
${(usageMap[ruleName] || []).map(u =>
|
|
244
|
+
`<li>${u.schema} → <strong>${u.field}</strong> (${u.rule})</li>`
|
|
245
|
+
).join("") || "<li>Not used</li>"}
|
|
246
|
+
</ul>
|
|
247
|
+
|
|
248
|
+
${rule?.docs?.notes ?
|
|
249
|
+
`
|
|
250
|
+
<h3>Notes</h3>
|
|
251
|
+
<div class="rule-notes">${rule?.docs?.notes}</div>
|
|
252
|
+
`
|
|
253
|
+
: ''}
|
|
254
|
+
</section>
|
|
255
|
+
`).join("")
|
|
256
|
+
).join("");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function renderRulesErrors(errors = {}) {
|
|
260
|
+
return `
|
|
261
|
+
<ul>
|
|
262
|
+
${Object.entries(errors).map(([k, v]) =>
|
|
263
|
+
`<li><a href="#error-${v.split('.')[0]}" class="tag error">${k}</a> ${v}</li>`
|
|
264
|
+
).join("")}
|
|
265
|
+
</ul>
|
|
266
|
+
`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function renderErrors(errorFiles, usageMap) {
|
|
270
|
+
return errorFiles.map(file =>
|
|
271
|
+
Object.entries(file.content).map(([errorName, errors]) => `
|
|
272
|
+
<section id="error-${errorName}" class="card">
|
|
273
|
+
<h2>Error: ${errorName} (${file.name})</h2>
|
|
274
|
+
<ul>
|
|
275
|
+
${Object.entries(errors).map(([key, value]) => `
|
|
276
|
+
<li><span class="tag error">${key}</span> ${value} </li>
|
|
277
|
+
`).join('')}
|
|
278
|
+
</ul>
|
|
279
|
+
|
|
280
|
+
<h3>Used by Rules</h3>
|
|
281
|
+
<ul>
|
|
282
|
+
${Object.entries(errors).map(([key, value]) => `
|
|
283
|
+
${(usageMap[`${errorName}.${key}`] || []).map(u =>
|
|
284
|
+
`<li>${u.file} → <strong>${u.field}</strong> (${u.error})</li>`
|
|
285
|
+
).join("") || "<li>Not used</li>"}
|
|
286
|
+
`).join('')}
|
|
287
|
+
</ul>
|
|
288
|
+
</section>
|
|
289
|
+
`).join('')).join('');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* ---------------------------------------
|
|
293
|
+
* CSS
|
|
294
|
+
* -------------------------------------*/
|
|
295
|
+
function generateCSS() {
|
|
296
|
+
return `
|
|
297
|
+
body {
|
|
298
|
+
margin: 0;
|
|
299
|
+
font-family: Inter, system-ui, sans-serif;
|
|
300
|
+
display: grid;
|
|
301
|
+
grid-template-columns: 280px 1fr;
|
|
302
|
+
background: #0f172a;
|
|
303
|
+
color: #e5e7eb;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.sidebar {
|
|
307
|
+
padding: 16px;
|
|
308
|
+
background: #020617;
|
|
309
|
+
overflow-y: auto;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.sidebar input {
|
|
313
|
+
width: 100%;
|
|
314
|
+
padding: 8px;
|
|
315
|
+
border-radius: 6px;
|
|
316
|
+
border: none;
|
|
317
|
+
margin-bottom: 16px;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.sidebar a {
|
|
321
|
+
display: block;
|
|
322
|
+
padding: 6px 10px;
|
|
323
|
+
color: #e5e7eb;
|
|
324
|
+
text-decoration: none;
|
|
325
|
+
border-radius: 6px;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.sidebar a:hover {
|
|
329
|
+
background: rgba(56,189,248,0.2);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
a {
|
|
333
|
+
color: #e5e7eb;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
main {
|
|
337
|
+
padding: 32px;
|
|
338
|
+
overflow-y: auto;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.card {
|
|
342
|
+
background: #020617;
|
|
343
|
+
border-radius: 12px;
|
|
344
|
+
padding: 24px;
|
|
345
|
+
margin-bottom: 32px;
|
|
346
|
+
border: 1px solid rgba(255,255,255,0.05);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.flow {
|
|
350
|
+
display: flex;
|
|
351
|
+
align-items: center;
|
|
352
|
+
gap: 12px;
|
|
353
|
+
flex-wrap: wrap;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.flow-step {
|
|
357
|
+
padding: 10px 14px;
|
|
358
|
+
border-radius: 8px;
|
|
359
|
+
background: rgba(56,189,248,0.15);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.flow-arrow {
|
|
363
|
+
opacity: 0.5;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.success {
|
|
367
|
+
background: rgba(34,197,94,0.2);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.tag {
|
|
371
|
+
padding: 4px 8px;
|
|
372
|
+
border-radius: 999px;
|
|
373
|
+
font-size: 14px;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.tag.error {
|
|
377
|
+
display: inline-block;
|
|
378
|
+
margin-bottom: 8px;
|
|
379
|
+
background: rgba(239,68,68,0.2);
|
|
380
|
+
text-decoration: none;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.tag.modifier {
|
|
384
|
+
display: inline-block;
|
|
385
|
+
font-size: 16px;
|
|
386
|
+
margin-bottom: 8px;
|
|
387
|
+
font-weight: 600;
|
|
388
|
+
background: rgba(167,139,250,0.2);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
table {
|
|
392
|
+
width: 100%;
|
|
393
|
+
border-collapse: collapse;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
td, th {
|
|
397
|
+
padding: 8px;
|
|
398
|
+
border-bottom: 1px solid rgba(255,255,255,0.05);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
th {
|
|
402
|
+
text-align: left;
|
|
403
|
+
border-bottom: 1px solid #f4f4f4;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.rule-notes {
|
|
407
|
+
padding: 16px;
|
|
408
|
+
color: black;
|
|
409
|
+
background: #f4f4f4;
|
|
410
|
+
border-radius: 12px;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.text-center {
|
|
414
|
+
text-align: center;
|
|
415
|
+
}
|
|
416
|
+
`;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/* ---------------------------------------
|
|
420
|
+
* CLIENT JS
|
|
421
|
+
* -------------------------------------*/
|
|
422
|
+
function generateClientJS() {
|
|
423
|
+
return `
|
|
424
|
+
const search = document.getElementById("search");
|
|
425
|
+
search.addEventListener("input", e => {
|
|
426
|
+
const value = e.target.value.toLowerCase();
|
|
427
|
+
document.querySelectorAll("section.card").forEach(card => {
|
|
428
|
+
card.style.display = card.innerText.toLowerCase().includes(value)
|
|
429
|
+
? ""
|
|
430
|
+
: "none";
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
`;
|
|
434
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "check-rule-mate",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./dist/main.cjs.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
"check rule",
|
|
18
18
|
"check rule mate"
|
|
19
19
|
],
|
|
20
|
+
"bin": {
|
|
21
|
+
"generate-docs": "./bin/generate-docs.js"
|
|
22
|
+
},
|
|
20
23
|
"scripts": {
|
|
21
24
|
"start": "node ./examples/vanilla/src/index.js",
|
|
22
25
|
"build": "node build",
|