check-rule-mate 0.5.8 → 0.5.9

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